eyeloupe 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +9 -3
- data/README.md +36 -7
- data/app/assets/builds/eyeloupe.css +1 -1
- data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/ai_assistant_controller.js +27 -0
- data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/refresh_controller.js +3 -1
- data/app/assets/stylesheets/application.tailwind.css +8 -0
- data/app/assets/stylesheets/eyeloupe/application.css +0 -1
- data/app/controllers/eyeloupe/ai_assistant_responses_controller.rb +30 -0
- data/app/controllers/eyeloupe/data_controller.rb +2 -0
- data/app/controllers/eyeloupe/exceptions_controller.rb +29 -0
- data/app/models/eyeloupe/exception.rb +6 -0
- data/app/models/eyeloupe/in_request.rb +1 -0
- data/app/models/eyeloupe/out_request.rb +1 -0
- data/app/views/eyeloupe/exceptions/_frame.html.erb +39 -0
- data/app/views/eyeloupe/exceptions/index.html.erb +13 -0
- data/app/views/eyeloupe/exceptions/show.html.erb +126 -0
- data/app/views/eyeloupe/in_requests/_frame.html.erb +1 -1
- data/app/views/eyeloupe/in_requests/index.html.erb +1 -1
- data/app/views/eyeloupe/in_requests/show.html.erb +17 -1
- data/app/views/eyeloupe/out_requests/_frame.html.erb +1 -1
- data/app/views/eyeloupe/out_requests/index.html.erb +1 -1
- data/app/views/eyeloupe/out_requests/show.html.erb +17 -1
- data/app/views/layouts/eyeloupe/application.html.erb +41 -5
- data/config/importmap.rb +2 -1
- data/config/routes.rb +2 -0
- data/db/migrate/20230604190442_create_eyeloupe_exceptions.rb +21 -0
- data/lib/eyeloupe/concerns/rescuable.rb +14 -0
- data/lib/eyeloupe/configuration.rb +7 -0
- data/lib/eyeloupe/engine.rb +46 -0
- data/lib/eyeloupe/processors/exception.rb +71 -0
- data/lib/eyeloupe/processors/in_request.rb +14 -3
- data/lib/eyeloupe/processors/out_request.rb +10 -20
- data/lib/eyeloupe/request_middleware.rb +13 -1
- data/lib/eyeloupe/version.rb +1 -1
- data/lib/eyeloupe.rb +3 -1
- metadata +26 -3
- data/lib/eyeloupe/http.rb +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a2fc3039c5a9cdcbd79a0a5639b74c52c3fc42f0c96fc569f92ce38255e7768
|
|
4
|
+
data.tar.gz: 4de09b66849266fa6e43af59a55a60ab60464bb1310f8dff5a28a8e84904dac9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c02695f8f4f26d09df3fda942691bf6729c9c1774b95acc1e9663e48c0ae25bad3d6991804a94163ff6595c8dbc8e56cb7f6dbd6f0be6eb195a2332446b5d9d
|
|
7
|
+
data.tar.gz: b61c44badf4b0713186338a76d0a246c1f87586d88e8404861931b326aafcfc8945977222e2296fc3d10aee2eb2f369df99cdcc84d4a8c9bb508453f579a6927
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
## 0.
|
|
1
|
+
## 0.3.0
|
|
2
2
|
|
|
3
|
-
-
|
|
3
|
+
- Add exceptions: Framework + ActiveJob + Sidekiq Worker exceptions.
|
|
4
|
+
- Add OpenAI support for AI assistant in exceptions.
|
|
5
|
+
- Fix exceptions when using importmap binary by adding net/http override in railties initializer.
|
|
4
6
|
|
|
5
7
|
## 0.2.0
|
|
6
8
|
|
|
7
|
-
- Fix missing require for `pagy` gem.
|
|
9
|
+
- Fix missing require for `pagy` gem.
|
|
10
|
+
|
|
11
|
+
## 0.1.0
|
|
12
|
+
|
|
13
|
+
- Initial release including incoming and outgoing requests.
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[![Gem Version]
|
|
1
|
+
[](https://badge.fury.io/rb/eyeloupe)
|
|
2
2
|
|
|
3
3
|
[![Contributors][contributors-shield]][contributors-url]
|
|
4
4
|
[![Forks][forks-shield]][forks-url]
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
<img src="app/assets/images/eyeloupe/logo.png" width=120 alt="Logo" >
|
|
13
13
|
</a>
|
|
14
14
|
|
|
15
|
-
<h3 align="center">Eyeloupe</h3>
|
|
15
|
+
<h3 align="center">Eyeloupe (beta)</h3>
|
|
16
16
|
|
|
17
17
|
<p align="center">
|
|
18
|
-
The elegant Rails debug assistant.
|
|
18
|
+
The elegant Rails debug assistant. AI powered.
|
|
19
19
|
<br />
|
|
20
20
|
<a href="https://github.com/alxlion/eyeloupe/issues">Report Bug</a>
|
|
21
21
|
·
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
[![Eyeloupe screenshot][eyeloupe-screen]](https://github.com/alxlion/eyeloupe)
|
|
27
27
|
|
|
28
|
-
Eyeloupe is the elegant Rails debug assistant. It helps you to debug your Rails application by providing a simple and elegant interface to view your incoming
|
|
28
|
+
Eyeloupe is the elegant Rails debug assistant. It helps you to debug your Rails application by providing a simple and elegant interface to view your incoming/outgoing requests and exceptions, powered by AI.
|
|
29
29
|
|
|
30
30
|
## Installation
|
|
31
31
|
Add this line to your application's Gemfile:
|
|
@@ -61,12 +61,24 @@ This is an example of the configuration you can add to your `initializers/eyelou
|
|
|
61
61
|
```ruby
|
|
62
62
|
Eyeloupe.configure do |config|
|
|
63
63
|
config.excluded_paths = %w[assets favicon.ico service-worker.js manifest.json]
|
|
64
|
-
config.capture =
|
|
64
|
+
config.capture = Rails.env.development?
|
|
65
|
+
config.openai_access_key = "your-openai-access-key"
|
|
66
|
+
config.openai_model = "gpt-4"
|
|
65
67
|
end
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
- `excluded_paths` is an array of paths you want to exclude from Eyeloupe capture. Eyeloupe adds these excluded paths to the default ones: ` %w[mini-profiler eyeloupe active_storage]`
|
|
69
71
|
- `capture` is a boolean to enable/disable Eyeloupe capture. By default, it's set to `true`.
|
|
72
|
+
- `openai_access_key` is the access key to use the OpenAI API. You can get one [here](https://platform.openai.com/).
|
|
73
|
+
- `openai_model` is the model to use for the OpenAI API. You can find the list of available models [here](https://platform.openai.com/docs/models).
|
|
74
|
+
|
|
75
|
+
### Exception handling
|
|
76
|
+
|
|
77
|
+
To be able to handle exceptions, be sure to disable the default Rails exception handling in your environment config file (e.g. `config/environments/development.rb`):
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
config.consider_all_requests_local = false
|
|
81
|
+
```
|
|
70
82
|
|
|
71
83
|
## Usage
|
|
72
84
|
|
|
@@ -82,6 +94,21 @@ By activating auto-fresh, every _3 seconds_ the page will be refreshed to show y
|
|
|
82
94
|
|
|
83
95
|
You can delete all the data stored by Eyeloupe by clicking on the trash button.
|
|
84
96
|
|
|
97
|
+
### AI Assistant
|
|
98
|
+
|
|
99
|
+
When you define an OpenAI access key in the configuration, you could see a new section in the exception details page. This section is powered by the OpenAI API and it's able to give you a solution to solve your exception.
|
|
100
|
+
It sends the entire content of the file containing the exception to have the best answer to your problem.
|
|
101
|
+
|
|
102
|
+

|
|
103
|
+
|
|
104
|
+
## Upgrade
|
|
105
|
+
|
|
106
|
+
When your upgrade Eyeloupe to the latest version, be sure to run the following commands:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
$ rails eyeloupe:install:migrations
|
|
110
|
+
$ rails db:migrate
|
|
111
|
+
```
|
|
85
112
|
|
|
86
113
|
## Q/A
|
|
87
114
|
|
|
@@ -95,8 +122,10 @@ Yes, Eyeloupe is inspired by Laravel Telescope. A lot of people coming from Lara
|
|
|
95
122
|
|
|
96
123
|
## Roadmap
|
|
97
124
|
|
|
98
|
-
- [
|
|
125
|
+
- [x] Exceptions - Track all the exceptions thrown by your application
|
|
126
|
+
- [x] AI assistant - Use OpenAI API to help you to solve your exceptions
|
|
99
127
|
- [ ] Custom links to the menu - To access all of your debug tool in one place (Sidekiq web, Mailhog, etc.)
|
|
128
|
+
- [ ] Refactoring / clean code - To make the code more readable and maintainable
|
|
100
129
|
|
|
101
130
|
## Contributing
|
|
102
131
|
Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
@@ -135,4 +164,4 @@ Project Link: [https://github.com/alxlion/eyeloupe](https://github.com/alxlion/e
|
|
|
135
164
|
[license-url]: https://github.com/alxlion/eyeloupe/blob/master/MIT-LICENSE.txt
|
|
136
165
|
[eyeloupe-screen]: /doc/img/screen.png
|
|
137
166
|
[gem-version]: https://badge.fury.io/rb/eyeloupe.svg
|
|
138
|
-
[gem-url]: https://rubygems.org/gems/eyeloupe
|
|
167
|
+
[gem-url]: https://rubygems.org/gems/eyeloupe
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/*! tailwindcss v3.1.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:Fira Sans,sans-serif,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}select{color-adjust:exact;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact}[multiple]{color-adjust:unset;background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset}[type=checkbox],[type=radio]{color-adjust:exact;--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:-webkit-sticky;position:sticky}.inset-0{bottom:0;left:0;right:0;top:0}.left-full{left:100%}.top-0{top:0}.z-50{z-index:50}.z-40{z-index:40}.-m-2\.5{margin:-.625rem}.-m-2{margin:-.5rem}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-my-2{margin-bottom:-.5rem;margin-top:-.5rem}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mt-4{margin-top:1rem}.mt-2{margin-top:.5rem}.mt-8{margin-top:2rem}.mt-6{margin-top:1.5rem}.mt-1{margin-top:.25rem}.mr-16{margin-right:4rem}.ml-2{margin-left:.5rem}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.flow-root{display:flow-root}.hidden{display:none}.h-6{height:1.5rem}.h-16{height:4rem}.h-7{height:1.75rem}.h-12{height:3rem}.h-5{height:1.25rem}.w-full{width:100%}.w-16{width:4rem}.w-6{width:1.5rem}.w-auto{width:auto}.w-5{width:1.25rem}.min-w-full{min-width:100%}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.flex-col{flex-direction:column}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-y-5{row-gap:1.25rem}.gap-y-7{row-gap:1.75rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(209 213 219/var(--tw-divide-opacity))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-t{border-top-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-purple-50{--tw-bg-opacity:1;background-color:rgb(250 245 255/var(--tw-bg-opacity))}.bg-gray-900\/80{background-color:#111827cc}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-1{padding:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-10{padding-bottom:2.5rem;padding-top:2.5rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pl-3{padding-left:.75rem}.pr-4{padding-right:1rem}.pt-5{padding-top:1.25rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.tracking-wide{letter-spacing:.025em}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-purple-700{--tw-text-opacity:1;color:rgb(126 34 206/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-inset{--tw-ring-inset:inset}.ring-green-600\/20{--tw-ring-color:#16a34a33}.ring-blue-700\/10{--tw-ring-color:#1d4ed81a}.ring-yellow-600\/20{--tw-ring-color:#ca8a0433}.ring-red-600\/10{--tw-ring-color:#dc26261a}.ring-gray-500\/10{--tw-ring-color:#6b72801a}.ring-gray-500{--tw-ring-opacity:1;--tw-ring-color:rgb(107 114 128/var(--tw-ring-opacity))}.ring-purple-700\/10{--tw-ring-color:#7e22ce1a}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-500{transition-duration:.5s}.pagination{display:inline-flex;position:relative;z-index:0}.pagination>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-1px*(1 - var(--tw-space-x-reverse)));margin-right:calc(-1px*var(--tw-space-x-reverse))}.pagination{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);border-radius:.375rem;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.pagination .prev a{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-bottom-left-radius:.375rem;border-color:rgb(209 213 219/var(--tw-border-opacity));border-top-left-radius:.375rem;border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;padding:.5rem;position:relative}.pagination .prev a:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .next a{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-bottom-right-radius:.375rem;border-color:rgb(209 213 219/var(--tw-border-opacity));border-top-right-radius:.375rem;border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;padding:.5rem;position:relative}.pagination .next a:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .current{--tw-text-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity));border-top-width:2px;color:rgb(239 68 68/var(--tw-text-opacity));padding-left:1rem;padding-right:1rem;padding-top:1rem}.pagination .current,.pagination a{--tw-border-opacity:1;align-items:center;display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem}.pagination a{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(209 213 219/var(--tw-border-opacity));border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));padding:.5rem 1rem;position:relative}.pagination a:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .disabled{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(209 213 219/var(--tw-border-opacity));border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;opacity:.4;padding:.5rem 1rem;position:relative}.pagination .disabled:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .gap{background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(209 213 219/var(--tw-border-opacity));color:rgb(55 65 81/var(--tw-text-opacity))}.pagination .active,.pagination .gap{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;border-width:1px;display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;padding:.5rem 1rem;position:relative}.pagination .active{background-color:rgb(254 242 242/var(--tw-bg-opacity));border-color:rgb(239 68 68/var(--tw-border-opacity));color:rgb(239 68 68/var(--tw-text-opacity));z-index:10}.pagination .next_page,.pagination .previous_page{display:none}.hover\:bg-red-600:hover{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity))}.hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.focus\:border-red-500:focus{--tw-border-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-red-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(239 68 68/var(--tw-ring-opacity))}@media (min-width:640px){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:mt-0{margin-top:0}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:flex-auto{flex:1 1 auto}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:items-center{align-items:center}.sm\:gap-4{gap:1rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-0{padding-right:0}.sm\:pl-0,.sm\:px-0{padding-left:0}.sm\:pr-0{padding-right:0}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:fixed{position:fixed}.lg\:inset-y-0{bottom:0;top:0}.lg\:z-50{z-index:50}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:w-72{width:18rem}.lg\:flex-col{flex-direction:column}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:pl-72{padding-left:18rem}}
|
|
1
|
+
/*! tailwindcss v3.1.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:Fira Sans,sans-serif,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}select{color-adjust:exact;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact}[multiple]{color-adjust:unset;background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset}[type=checkbox],[type=radio]{color-adjust:exact;--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:-webkit-sticky;position:sticky}.inset-0{bottom:0;left:0;right:0;top:0}.left-full{left:100%}.top-0{top:0}.z-50{z-index:50}.z-40{z-index:40}.col-span-1{grid-column:span 1/span 1}.col-span-11{grid-column:span 11/span 11}.-m-2\.5{margin:-.625rem}.-m-2{margin:-.5rem}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-my-2{margin-bottom:-.5rem;margin-top:-.5rem}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mt-4{margin-top:1rem}.mt-2{margin-top:.5rem}.mt-8{margin-top:2rem}.mt-6{margin-top:1.5rem}.mt-1{margin-top:.25rem}.mr-16{margin-right:4rem}.ml-2{margin-left:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.h-6{height:1.5rem}.h-16{height:4rem}.h-7{height:1.75rem}.h-12{height:3rem}.h-5{height:1.25rem}.w-full{width:100%}.w-16{width:4rem}.w-6{width:1.5rem}.w-auto{width:auto}.w-5{width:1.25rem}.min-w-full{min-width:100%}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.gap-y-5{row-gap:1.25rem}.gap-y-7{row-gap:1.75rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(209 213 219/var(--tw-divide-opacity))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-t{border-top-width:1px}.border-b{border-bottom-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-purple-50{--tw-bg-opacity:1;background-color:rgb(250 245 255/var(--tw-bg-opacity))}.bg-gray-900\/80{background-color:#111827cc}.bg-opacity-30{--tw-bg-opacity:0.3}.p-2{padding:.5rem}.\!p-0{padding:0!important}.p-2\.5{padding:.625rem}.p-1{padding:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-0{padding-left:0;padding-right:0}.py-1{padding-bottom:.25rem;padding-top:.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-10{padding-bottom:2.5rem;padding-top:2.5rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pl-3{padding-left:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pb-3{padding-bottom:.75rem}.pt-5{padding-top:1.25rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.tracking-wide{letter-spacing:.025em}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-purple-700{--tw-text-opacity:1;color:rgb(126 34 206/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.underline{-webkit-text-decoration-line:underline;text-decoration-line:underline}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-inset{--tw-ring-inset:inset}.ring-green-600\/20{--tw-ring-color:#16a34a33}.ring-blue-700\/10{--tw-ring-color:#1d4ed81a}.ring-yellow-600\/20{--tw-ring-color:#ca8a0433}.ring-red-600\/10{--tw-ring-color:#dc26261a}.ring-gray-500\/10{--tw-ring-color:#6b72801a}.ring-gray-500{--tw-ring-opacity:1;--tw-ring-color:rgb(107 114 128/var(--tw-ring-opacity))}.ring-purple-700\/10{--tw-ring-color:#7e22ce1a}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-500{transition-duration:.5s}.pagination{display:inline-flex;position:relative;z-index:0}.pagination>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(-1px*(1 - var(--tw-space-x-reverse)));margin-right:calc(-1px*var(--tw-space-x-reverse))}.pagination{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);border-radius:.375rem;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.pagination .prev a{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-bottom-left-radius:.375rem;border-color:rgb(209 213 219/var(--tw-border-opacity));border-top-left-radius:.375rem;border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;padding:.5rem;position:relative}.pagination .prev a:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .next a{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-bottom-right-radius:.375rem;border-color:rgb(209 213 219/var(--tw-border-opacity));border-top-right-radius:.375rem;border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;padding:.5rem;position:relative}.pagination .next a:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .current{--tw-text-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity));border-top-width:2px;color:rgb(239 68 68/var(--tw-text-opacity));padding-left:1rem;padding-right:1rem;padding-top:1rem}.pagination .current,.pagination a{--tw-border-opacity:1;align-items:center;display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem}.pagination a{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(209 213 219/var(--tw-border-opacity));border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));padding:.5rem 1rem;position:relative}.pagination a:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .disabled{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(209 213 219/var(--tw-border-opacity));border-width:1px;color:rgb(107 114 128/var(--tw-text-opacity));display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;opacity:.4;padding:.5rem 1rem;position:relative}.pagination .disabled:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.pagination .gap{background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(209 213 219/var(--tw-border-opacity));color:rgb(55 65 81/var(--tw-text-opacity))}.pagination .active,.pagination .gap{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;border-width:1px;display:inline-flex;font-size:.875rem;font-weight:500;line-height:1.25rem;padding:.5rem 1rem;position:relative}.pagination .active{background-color:rgb(254 242 242/var(--tw-bg-opacity));border-color:rgb(239 68 68/var(--tw-border-opacity));color:rgb(239 68 68/var(--tw-text-opacity));z-index:10}.pagination .next_page,.pagination .previous_page{display:none}#result pre{background-color:rgb(0 0 0/var(--tw-bg-opacity));color:rgb(255 255 255/var(--tw-text-opacity));margin-bottom:.5rem;margin-top:.5rem;padding:.75rem}#result p code,#result pre{--tw-bg-opacity:1;--tw-text-opacity:1;border-radius:.375rem}#result p code{background-color:rgb(229 231 235/var(--tw-bg-opacity));color:rgb(75 85 99/var(--tw-text-opacity));font-size:.875rem;line-height:1.25rem;padding:.125rem .5rem}.hover\:bg-red-600:hover{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity))}.hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:bg-red-400:hover{--tw-bg-opacity:1;background-color:rgb(248 113 113/var(--tw-bg-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:border-red-500:focus{--tw-border-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-red-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(239 68 68/var(--tw-ring-opacity))}@media (min-width:640px){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:mt-0{margin-top:0}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:flex-auto{flex:1 1 auto}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:items-center{align-items:center}.sm\:gap-4{gap:1rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-0{padding-right:0}.sm\:pl-0,.sm\:px-0{padding-left:0}.sm\:pr-0{padding-right:0}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:fixed{position:fixed}.lg\:inset-y-0{bottom:0;top:0}.lg\:z-50{z-index:50}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:w-72{width:18rem}.lg\:flex-col{flex-direction:column}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:pl-72{padding-left:18rem}}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import showdown from "showdown"
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["result", "loader", "btn"]
|
|
5
|
+
static values = { url: String }
|
|
6
|
+
|
|
7
|
+
assist() {
|
|
8
|
+
this.btnTarget.classList.add("hidden")
|
|
9
|
+
this.resultTarget.innerHTML = ""
|
|
10
|
+
this.loaderTarget.classList.remove("hidden")
|
|
11
|
+
|
|
12
|
+
fetch(this.urlValue)
|
|
13
|
+
.then(response => response.json())
|
|
14
|
+
.then(json => {
|
|
15
|
+
let result = json.choices[0].message.content
|
|
16
|
+
let converter = new showdown.Converter()
|
|
17
|
+
this.resultTarget.innerHTML = converter.makeHtml(result)
|
|
18
|
+
|
|
19
|
+
})
|
|
20
|
+
.catch(error => {
|
|
21
|
+
this.resultTarget.innerHTML = error
|
|
22
|
+
})
|
|
23
|
+
.finally(() => {
|
|
24
|
+
this.loaderTarget.classList.add("hidden")
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -39,4 +39,12 @@
|
|
|
39
39
|
|
|
40
40
|
.pagination .previous_page, .pagination .next_page {
|
|
41
41
|
@apply hidden;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#result pre {
|
|
45
|
+
@apply bg-black text-white p-3 rounded-md my-2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#result p code {
|
|
49
|
+
@apply bg-gray-200 text-gray-600 px-2 py-0.5 text-sm rounded-md;
|
|
42
50
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Eyeloupe
|
|
4
|
+
class AiAssistantResponsesController < ApplicationController
|
|
5
|
+
|
|
6
|
+
before_action :set_exception, only: %i[ show ]
|
|
7
|
+
|
|
8
|
+
def show
|
|
9
|
+
client = OpenAI::Client.new
|
|
10
|
+
|
|
11
|
+
code = File.read(@exception.file)
|
|
12
|
+
|
|
13
|
+
@response = client.chat(
|
|
14
|
+
parameters: {
|
|
15
|
+
model: Eyeloupe::configuration.openai_model,
|
|
16
|
+
messages: [{"role": "system", "content": "You are a Ruby on Rails software developer, you develop software programs and applications using programming languages like Ruby and Ruby on Rails and development tools."},
|
|
17
|
+
{"role": "user", "content": "I have a problem with my Ruby on Rails application. I am getting an error message that says: #{@exception.kind} #{@exception.message}. Here is my code, the error is in line #{@exception.line}: #{code}. Answer as concise as possible. Show me resulting code. The response should in Markdown format."}],
|
|
18
|
+
temperature: 0.7
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
render json: @response
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def set_exception
|
|
27
|
+
@exception = Exception.find(params[:id])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Eyeloupe
|
|
4
|
+
class ExceptionsController < ApplicationController
|
|
5
|
+
|
|
6
|
+
before_action :set_exception, only: %i[ show ]
|
|
7
|
+
|
|
8
|
+
# GET /out_requests
|
|
9
|
+
def index
|
|
10
|
+
@pagy, @exceptions = pagy(Exception.all.order(created_at: :desc), items: 50)
|
|
11
|
+
|
|
12
|
+
render partial: 'frame' if params[:frame].present?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# GET /out_requests/1
|
|
16
|
+
def show
|
|
17
|
+
start = @exception.line - 5
|
|
18
|
+
start = 0 if start < 0
|
|
19
|
+
@line_numbers = [*start..@exception.line+6]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
# Use callbacks to share common setup or constraints between actions.
|
|
24
|
+
def set_exception
|
|
25
|
+
@exception = Exception.find(params[:id])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<%= turbo_frame_tag "frame" do %>
|
|
2
|
+
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
|
3
|
+
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
|
4
|
+
|
|
5
|
+
<table class="min-w-full divide-y divide-gray-300">
|
|
6
|
+
<thead>
|
|
7
|
+
<tr>
|
|
8
|
+
<th scope="col" class="py-3 pl-4 pr-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500 sm:pl-0">Kind</th>
|
|
9
|
+
<th scope="col" class="px-3 py-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500">Count</th>
|
|
10
|
+
<th scope="col" class="px-3 py-3 text-left text-sm font-medium uppercase tracking-wide text-gray-500">Occured</th>
|
|
11
|
+
<th scope="col" class="relative py-3 pl-3 pr-4 sm:pr-0">
|
|
12
|
+
<span class="sr-only">Details</span>
|
|
13
|
+
</th>
|
|
14
|
+
</tr>
|
|
15
|
+
</thead>
|
|
16
|
+
<tbody class="divide-y divide-gray-200">
|
|
17
|
+
<% @exceptions.each do |ex| %>
|
|
18
|
+
<tr>
|
|
19
|
+
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-base font-medium text-gray-900 sm:pl-0">
|
|
20
|
+
<%= ex.kind %>
|
|
21
|
+
<p class="text-sm font-normal text-gray-500"><%= ex.message.truncate(100) %></p>
|
|
22
|
+
</td>
|
|
23
|
+
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= ex.count %></td>
|
|
24
|
+
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= distance_of_time_in_words(ex.updated_at, DateTime.now) %></td>
|
|
25
|
+
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-base font-medium sm:pr-0">
|
|
26
|
+
<%= link_to "Details", exception_path(ex), class: "text-gray-600 hover:text-gray-900", data: {"turbo_frame": "_top"} %>
|
|
27
|
+
</td>
|
|
28
|
+
</tr>
|
|
29
|
+
<% end %>
|
|
30
|
+
</tbody>
|
|
31
|
+
</table>
|
|
32
|
+
</div>
|
|
33
|
+
<aside class="mt-4 px-4 py-3 flex items-center justify-center sm:px-6" aria-label="Pagination">
|
|
34
|
+
<div class="flex-1 flex justify-center">
|
|
35
|
+
<%== pagy_nav(@pagy) %>
|
|
36
|
+
</div>
|
|
37
|
+
</aside>
|
|
38
|
+
</div>
|
|
39
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
|
+
<div class="sm:flex sm:items-center">
|
|
3
|
+
<div class="sm:flex-auto">
|
|
4
|
+
<h1 class="text-xl font-semibold leading-6 text-gray-900">Exceptions</h1>
|
|
5
|
+
<p class="mt-2 text-sm text-gray-700">All exceptions that have been raised in the application.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<div>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="mt-8 flow-root">
|
|
11
|
+
<%= turbo_frame_tag "frame", src: exceptions_path(frame: true), data: {"eyeloupe--refresh-target": "frame"} do %><% end %>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
|
+
<div class="px-4 sm:px-0">
|
|
3
|
+
<h3 class="text-xl font-semibold leading-7 text-gray-900">Exception Details</h3>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="mt-6 border-t border-gray-100">
|
|
6
|
+
<dl class="divide-y divide-gray-100">
|
|
7
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
8
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Time</dt>
|
|
9
|
+
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
10
|
+
<%= @exception.created_at.to_formatted_s(:long) %> (<%= distance_of_time_in_words(@exception.updated_at, DateTime.now) %>)
|
|
11
|
+
</dd>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
14
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Hostname</dt>
|
|
15
|
+
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0"><%= @exception.hostname %></dd>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
18
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Kind</dt>
|
|
19
|
+
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
20
|
+
<%= @exception.kind %>
|
|
21
|
+
</dd>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
24
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Count</dt>
|
|
25
|
+
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
26
|
+
<%= @exception.count %>
|
|
27
|
+
</dd>
|
|
28
|
+
</div>
|
|
29
|
+
<% if @exception.in_request_id.present? %>
|
|
30
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
31
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Request</dt>
|
|
32
|
+
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
33
|
+
<%= link_to "View request", in_request_path(@exception.in_request_id), class: "underline" %>
|
|
34
|
+
</dd>
|
|
35
|
+
</div>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% if @exception.out_request_id.present? %>
|
|
38
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
39
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Request</dt>
|
|
40
|
+
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
41
|
+
<%= link_to "View request", out_request_path(@exception.out_request_id), class: "underline" %>
|
|
42
|
+
</dd>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
46
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Message</dt>
|
|
47
|
+
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto">
|
|
48
|
+
<pre class="p-2"><%= @exception.message %></pre>
|
|
49
|
+
</dd>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
52
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Backtrace</dt>
|
|
53
|
+
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto">
|
|
54
|
+
<pre class="p-2"><%= JSON.parse(@exception.stacktrace).join("\n") %></pre>
|
|
55
|
+
</dd>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
58
|
+
<dt class="text-base font-medium leading-6 text-gray-900">Code preview</dt>
|
|
59
|
+
<dd class="mt-1 text-base leading-6 sm:col-span-2 sm:mt-0 rounded-md bg-black text-white overflow-x-auto px-2 pt-2">
|
|
60
|
+
<p class="pb-4 font-semibold"><%= @exception.file %></p>
|
|
61
|
+
<% JSON.parse(@exception.location).each_with_index do |line, i| %>
|
|
62
|
+
<div class="grid grid-cols-12 gap-x-1 <%= @line_numbers[i] + 1 == @exception.line ? "bg-red-500 bg-opacity-30" : "" %>">
|
|
63
|
+
<span class="text-right col-span-1 font-medium text-base text-gray-300"><%= @line_numbers[i] + 1 %></span>
|
|
64
|
+
<pre class="col-span-11"><code class="!p-0"><%= line %></code></pre>
|
|
65
|
+
</div>
|
|
66
|
+
<% end %>
|
|
67
|
+
</dd>
|
|
68
|
+
</div>
|
|
69
|
+
<% if Eyeloupe::configuration.openai_access_key.present? %>
|
|
70
|
+
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0" data-controller="eyeloupe--ai-assistant" data-eyeloupe--ai-assistant-url-value="<%= ai_assistant_response_url(@exception) %>">
|
|
71
|
+
<dt class="text-base font-medium leading-6 text-gray-900">AI Assistant</dt>
|
|
72
|
+
<dd class="mt-1 text-base leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
73
|
+
<button data-action="eyeloupe--ai-assistant#assist" data-eyeloupe--ai-assistant-target="btn" class="rounded-md px-2 py-1 text-white bg-red-500 text-xl hover:shadow-md hover:bg-red-400 transition flex gap-x-2">
|
|
74
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-robot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
75
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
76
|
+
<path d="M7 7h10a2 2 0 0 1 2 2v1l1 1v3l-1 1v3a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-3l-1 -1v-3l1 -1v-1a2 2 0 0 1 2 -2z"></path>
|
|
77
|
+
<path d="M10 16h4"></path>
|
|
78
|
+
<circle cx="8.5" cy="11.5" r=".5" fill="currentColor"></circle>
|
|
79
|
+
<circle cx="15.5" cy="11.5" r=".5" fill="currentColor"></circle>
|
|
80
|
+
<path d="M9 7l-1 -4"></path>
|
|
81
|
+
<path d="M15 7l1 -4"></path>
|
|
82
|
+
</svg>
|
|
83
|
+
<span>Click to get some help</span>
|
|
84
|
+
</button>
|
|
85
|
+
|
|
86
|
+
<svg data-eyeloupe--ai-assistant-target="loader" width="50" class="hidden" viewBox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="#ff3130">
|
|
87
|
+
<circle cx="15" cy="15" r="15">
|
|
88
|
+
<animate attributeName="r" from="15" to="15"
|
|
89
|
+
begin="0s" dur="0.8s"
|
|
90
|
+
values="15;9;15" calcMode="linear"
|
|
91
|
+
repeatCount="indefinite" />
|
|
92
|
+
<animate attributeName="fill-opacity" from="1" to="1"
|
|
93
|
+
begin="0s" dur="0.8s"
|
|
94
|
+
values="1;.5;1" calcMode="linear"
|
|
95
|
+
repeatCount="indefinite" />
|
|
96
|
+
</circle>
|
|
97
|
+
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
|
|
98
|
+
<animate attributeName="r" from="9" to="9"
|
|
99
|
+
begin="0s" dur="0.8s"
|
|
100
|
+
values="9;15;9" calcMode="linear"
|
|
101
|
+
repeatCount="indefinite" />
|
|
102
|
+
<animate attributeName="fill-opacity" from="0.5" to="0.5"
|
|
103
|
+
begin="0s" dur="0.8s"
|
|
104
|
+
values=".5;1;.5" calcMode="linear"
|
|
105
|
+
repeatCount="indefinite" />
|
|
106
|
+
</circle>
|
|
107
|
+
<circle cx="105" cy="15" r="15">
|
|
108
|
+
<animate attributeName="r" from="15" to="15"
|
|
109
|
+
begin="0s" dur="0.8s"
|
|
110
|
+
values="15;9;15" calcMode="linear"
|
|
111
|
+
repeatCount="indefinite" />
|
|
112
|
+
<animate attributeName="fill-opacity" from="1" to="1"
|
|
113
|
+
begin="0s" dur="0.8s"
|
|
114
|
+
values="1;.5;1" calcMode="linear"
|
|
115
|
+
repeatCount="indefinite" />
|
|
116
|
+
</circle>
|
|
117
|
+
</svg>
|
|
118
|
+
|
|
119
|
+
<p data-eyeloupe--ai-assistant-target="result" id="result"></p>
|
|
120
|
+
</dd>
|
|
121
|
+
</div>
|
|
122
|
+
<% end %>
|
|
123
|
+
</dl>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
</div>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-base font-medium text-gray-900 sm:pl-0">
|
|
22
22
|
<%= render "eyeloupe/shared/verb", verb: request.verb %>
|
|
23
23
|
</td>
|
|
24
|
-
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.path.truncate(
|
|
24
|
+
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.path.truncate(100) %></td>
|
|
25
25
|
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500">
|
|
26
26
|
<%= render "eyeloupe/shared/status_code", code: request.status %>
|
|
27
27
|
</td>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div data-controller="eyeloupe--search">
|
|
1
|
+
<div data-controller="eyeloupe--search" class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
2
|
<div class="sm:flex sm:items-center">
|
|
3
3
|
<div class="sm:flex-auto">
|
|
4
4
|
<h1 class="text-xl font-semibold leading-6 text-gray-900">Requests</h1>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div>
|
|
1
|
+
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
2
|
<div class="px-4 sm:px-0">
|
|
3
3
|
<h3 class="text-xl font-semibold leading-7 text-gray-900">Request details</h3>
|
|
4
4
|
</div>
|
|
@@ -80,3 +80,19 @@
|
|
|
80
80
|
</dl>
|
|
81
81
|
</div>
|
|
82
82
|
</div>
|
|
83
|
+
|
|
84
|
+
<% if @request.exception.present? %>
|
|
85
|
+
<div class="mt-6 bg-white rounded-md shadow-md py-5">
|
|
86
|
+
<h3 class="text-base font-semibold leading-6 text-gray-900 px-4 sm:px-6 lg:px-8 border-b border-gray-100 pb-3">Exceptions</h3>
|
|
87
|
+
|
|
88
|
+
<%= link_to exception_path(@request.exception), class: "text-gray-600 hover:text-gray-900 hover:bg-gray-100 block px-4 sm:px-6 lg:px-8", data: {"turbo_frame": "_top"} do %>
|
|
89
|
+
<div class="divide-y divide-gray-100 px-0 flex justify-between">
|
|
90
|
+
<div class="whitespace-nowrap py-4 pl-4 pr-3 text-base font-medium text-gray-900 sm:pl-0">
|
|
91
|
+
<%= @request.exception.kind %>
|
|
92
|
+
<p class="text-sm font-normal text-gray-500"><%= @request.exception.message %></p>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<% end %>
|
|
96
|
+
|
|
97
|
+
</div>
|
|
98
|
+
<% end %>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<%= render "eyeloupe/shared/verb", verb: request.verb %>
|
|
24
24
|
</td>
|
|
25
25
|
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.hostname %></td>
|
|
26
|
-
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.path.truncate(
|
|
26
|
+
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.path.truncate(100) %></td>
|
|
27
27
|
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500">
|
|
28
28
|
<%= render "eyeloupe/shared/status_code", code: request.status %>
|
|
29
29
|
</td>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div data-controller="eyeloupe--search">
|
|
1
|
+
<div data-controller="eyeloupe--search" class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
2
|
<div class="sm:flex sm:items-center">
|
|
3
3
|
<div class="sm:flex-auto">
|
|
4
4
|
<h1 class="text-xl font-semibold leading-6 text-gray-900">HTTP Client</h1>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div>
|
|
1
|
+
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
2
|
<div class="px-4 sm:px-0">
|
|
3
3
|
<h3 class="text-xl font-semibold leading-7 text-gray-900">HTTP Client Request Details</h3>
|
|
4
4
|
</div>
|
|
@@ -67,3 +67,19 @@
|
|
|
67
67
|
</dl>
|
|
68
68
|
</div>
|
|
69
69
|
</div>
|
|
70
|
+
|
|
71
|
+
<% if @request.exception.present? %>
|
|
72
|
+
<div class="mt-6 bg-white rounded-md shadow-md py-5">
|
|
73
|
+
<h3 class="text-base font-semibold leading-6 text-gray-900 px-4 sm:px-6 lg:px-8 border-b border-gray-100 pb-3">Exceptions</h3>
|
|
74
|
+
|
|
75
|
+
<%= link_to exception_path(@request.exception), class: "text-gray-600 hover:text-gray-900 hover:bg-gray-100 block px-4 sm:px-6 lg:px-8", data: {"turbo_frame": "_top"} do %>
|
|
76
|
+
<div class="divide-y divide-gray-100 px-0 flex justify-between">
|
|
77
|
+
<div class="whitespace-nowrap py-4 pl-4 pr-3 text-base font-medium text-gray-900 sm:pl-0">
|
|
78
|
+
<%= @request.exception.kind %>
|
|
79
|
+
<p class="text-sm font-normal text-gray-500"><%= @request.exception.message %></p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<% end %>
|
|
83
|
+
|
|
84
|
+
</div>
|
|
85
|
+
<% end %>
|
|
@@ -67,6 +67,23 @@
|
|
|
67
67
|
HTTP Client
|
|
68
68
|
<% end %>
|
|
69
69
|
</li>
|
|
70
|
+
<li>
|
|
71
|
+
<%= link_to exceptions_path, class:"#{request.path.include?('/exceptions') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %>
|
|
72
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
73
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
74
|
+
<path d="M9 9v-1a3 3 0 0 1 6 0v1"></path>
|
|
75
|
+
<path d="M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3"></path>
|
|
76
|
+
<path d="M3 13l4 0"></path>
|
|
77
|
+
<path d="M17 13l4 0"></path>
|
|
78
|
+
<path d="M12 20l0 -6"></path>
|
|
79
|
+
<path d="M4 19l3.35 -2"></path>
|
|
80
|
+
<path d="M20 19l-3.35 -2"></path>
|
|
81
|
+
<path d="M4 7l3.75 2.4"></path>
|
|
82
|
+
<path d="M20 7l-3.75 2.4"></path>
|
|
83
|
+
</svg>
|
|
84
|
+
Exceptions
|
|
85
|
+
<% end %>
|
|
86
|
+
</li>
|
|
70
87
|
</ul>
|
|
71
88
|
</li>
|
|
72
89
|
</ul>
|
|
@@ -84,7 +101,7 @@
|
|
|
84
101
|
<%= image_tag "eyeloupe/logo.png", class: "h-12 w-auto" %>
|
|
85
102
|
<h1 class="ml-2 text-2xl font-semibold text-gray-700">Eyeloupe</h1>
|
|
86
103
|
<% end %>
|
|
87
|
-
<nav class="flex flex-1 flex-col">
|
|
104
|
+
<nav class="flex flex-1 flex-col mt-4">
|
|
88
105
|
<ul role="list" class="flex flex-1 flex-col gap-y-7">
|
|
89
106
|
<li>
|
|
90
107
|
<ul role="list" class="-mx-2 space-y-1">
|
|
@@ -114,6 +131,23 @@
|
|
|
114
131
|
HTTP Client
|
|
115
132
|
<% end %>
|
|
116
133
|
</li>
|
|
134
|
+
<li>
|
|
135
|
+
<%= link_to exceptions_path, class:"#{request.path.include?('/exceptions') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %>
|
|
136
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
137
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
138
|
+
<path d="M9 9v-1a3 3 0 0 1 6 0v1"></path>
|
|
139
|
+
<path d="M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3"></path>
|
|
140
|
+
<path d="M3 13l4 0"></path>
|
|
141
|
+
<path d="M17 13l4 0"></path>
|
|
142
|
+
<path d="M12 20l0 -6"></path>
|
|
143
|
+
<path d="M4 19l3.35 -2"></path>
|
|
144
|
+
<path d="M20 19l-3.35 -2"></path>
|
|
145
|
+
<path d="M4 7l3.75 2.4"></path>
|
|
146
|
+
<path d="M20 7l-3.75 2.4"></path>
|
|
147
|
+
</svg>
|
|
148
|
+
Exceptions
|
|
149
|
+
<% end %>
|
|
150
|
+
</li>
|
|
117
151
|
</ul>
|
|
118
152
|
</li>
|
|
119
153
|
</ul>
|
|
@@ -191,11 +225,13 @@
|
|
|
191
225
|
</div>
|
|
192
226
|
|
|
193
227
|
<main class="py-10 lg:pl-72 px-5 mx-auto">
|
|
194
|
-
|
|
195
|
-
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
196
|
-
<%= yield %>
|
|
197
|
-
</div>
|
|
228
|
+
<%= yield %>
|
|
198
229
|
</main>
|
|
230
|
+
|
|
231
|
+
<footer class="text-gray-500 py-1 lg:pl-72 px-5 mx-auto text-sm">
|
|
232
|
+
Eyeloupe <%= Eyeloupe::VERSION %> - <a class="underline" href="https://github.com/alxlion/eyeloupe">Github</a>
|
|
233
|
+
</footer>
|
|
234
|
+
|
|
199
235
|
</div>
|
|
200
236
|
</div>
|
|
201
237
|
|
data/config/importmap.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
pin_all_from File.expand_path("../app/assets/javascripts", __dir__)
|
|
2
2
|
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
|
|
3
3
|
pin "@hotwired/stimulus", to: "https://ga.jspm.io/npm:@hotwired/stimulus@3.0.1/dist/stimulus.js"
|
|
4
|
-
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
|
4
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
|
5
|
+
pin "showdown", to: "https://ga.jspm.io/npm:showdown@2.1.0/dist/showdown.js"
|
data/config/routes.rb
CHANGED
|
@@ -4,6 +4,8 @@ Eyeloupe::Engine.routes.draw do
|
|
|
4
4
|
|
|
5
5
|
resources :in_requests, only: [:index, :show]
|
|
6
6
|
resources :out_requests, only: [:index, :show]
|
|
7
|
+
resources :exceptions, only: [:index, :show]
|
|
8
|
+
resources :ai_assistant_responses, only: [:show]
|
|
7
9
|
|
|
8
10
|
resource :data, only: [:destroy]
|
|
9
11
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CreateEyeloupeExceptions < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :eyeloupe_exceptions do |t|
|
|
4
|
+
t.string :hostname
|
|
5
|
+
t.string :kind
|
|
6
|
+
t.string :location
|
|
7
|
+
t.string :file
|
|
8
|
+
t.integer :line
|
|
9
|
+
t.text :stacktrace
|
|
10
|
+
t.string :message
|
|
11
|
+
t.integer :count, default: 1
|
|
12
|
+
t.text :full_message
|
|
13
|
+
t.references :in_request, null: true, foreign_key: { to_table: :eyeloupe_in_requests }
|
|
14
|
+
t.references :out_request, null: true, foreign_key: { to_table: :eyeloupe_out_requests }
|
|
15
|
+
|
|
16
|
+
t.timestamps
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
add_index :eyeloupe_exceptions, [:kind, :file, :line]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -11,9 +11,16 @@ module Eyeloupe
|
|
|
11
11
|
# @return [Boolean]
|
|
12
12
|
attr_accessor :capture
|
|
13
13
|
|
|
14
|
+
# @return [String]
|
|
15
|
+
attr_accessor :openai_access_key
|
|
16
|
+
|
|
17
|
+
# @return [String]
|
|
18
|
+
attr_accessor :openai_model
|
|
19
|
+
|
|
14
20
|
def initialize
|
|
15
21
|
@excluded_paths = %w[]
|
|
16
22
|
@capture = true
|
|
23
|
+
@openai_model = "gpt-3.5-turbo"
|
|
17
24
|
end
|
|
18
25
|
end
|
|
19
26
|
|
data/lib/eyeloupe/engine.rb
CHANGED
|
@@ -12,6 +12,52 @@ module Eyeloupe
|
|
|
12
12
|
app.config.middleware.insert(0, Eyeloupe::RequestMiddleware)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
initializer 'eyeloupe.active_job' do
|
|
16
|
+
ActiveSupport.on_load(:active_job) do
|
|
17
|
+
include Eyeloupe::Concerns::Rescuable
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
initializer "eyeloupe.configure_openai" do
|
|
22
|
+
OpenAI.configure do |config|
|
|
23
|
+
config.access_token = Eyeloupe::configuration.openai_access_key
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
initializer "eyeloupe.configure_sidekiq" do
|
|
28
|
+
if defined?(Sidekiq)
|
|
29
|
+
Sidekiq.configure_server do |config|
|
|
30
|
+
config.error_handlers << proc {|ex,ctx_hash| Eyeloupe::Processors::Exception.instance.process(nil, ex) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
initializer 'eyeloupe.override_net_http_request' do
|
|
36
|
+
require 'net/http'
|
|
37
|
+
Net::HTTP.class_eval do
|
|
38
|
+
alias original_request request
|
|
39
|
+
def request(req, body = nil, &block)
|
|
40
|
+
res, ex = nil
|
|
41
|
+
exception_processor = Eyeloupe::Processors::Exception.instance
|
|
42
|
+
out_request_processor = Eyeloupe::Processors::OutRequest.instance
|
|
43
|
+
|
|
44
|
+
if Eyeloupe.configuration.capture
|
|
45
|
+
begin
|
|
46
|
+
out_request_processor.init(req, body)
|
|
47
|
+
res = original_request(req, body, &block)
|
|
48
|
+
rescue => e
|
|
49
|
+
ex = exception_processor.process(nil, e)
|
|
50
|
+
ensure
|
|
51
|
+
out_request_processor.process(res, ex)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
res = original_request(req, body, &block)
|
|
55
|
+
end
|
|
56
|
+
res
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
15
61
|
initializer "eyeloupe.importmap", :before => "importmap" do |app|
|
|
16
62
|
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
17
63
|
# https://github.com/rails/importmap-rails#sweeping-the-cache-in-development-and-test
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
module Eyeloupe
|
|
3
|
+
module Processors
|
|
4
|
+
class Exception
|
|
5
|
+
include Singleton
|
|
6
|
+
|
|
7
|
+
# @param [Hash, nil] env Rack environment
|
|
8
|
+
# @param [Exception] exception The exception object
|
|
9
|
+
# @return [Eyeloupe::Exception] The exception model
|
|
10
|
+
def process(env, exception)
|
|
11
|
+
if env && env['action_dispatch.backtrace_cleaner'].present?
|
|
12
|
+
backtrace = env['action_dispatch.backtrace_cleaner'].filter(exception.backtrace)
|
|
13
|
+
backtrace = exception.backtrace if backtrace.blank?
|
|
14
|
+
else
|
|
15
|
+
backtrace = exception.backtrace
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
file = backtrace ? backtrace[0].split(":")[0] : ""
|
|
19
|
+
line = backtrace ? backtrace[0].split(":")[1].to_i : 0
|
|
20
|
+
|
|
21
|
+
create_or_update_exception(exception.class.name || "", file, line, backtrace, exception.message, exception.full_message)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
# @param [Array] trace The backtrace
|
|
27
|
+
# @return [Array] The source code lines
|
|
28
|
+
def read_file(trace)
|
|
29
|
+
file = trace.size > 0 ? trace[0].split(":")[0] : ""
|
|
30
|
+
line = trace.size > 0 ? trace[0].split(":")[1].to_i : 0
|
|
31
|
+
|
|
32
|
+
if File.exist?(file)
|
|
33
|
+
lines = File.readlines(file)
|
|
34
|
+
start = line - 5
|
|
35
|
+
start = 0 if start < 0
|
|
36
|
+
lines[start..line+5] || []
|
|
37
|
+
else
|
|
38
|
+
[]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @param [String] kind The exception class name
|
|
43
|
+
# @param [String] file The file path
|
|
44
|
+
# @param [Integer] line The line number
|
|
45
|
+
# @param [Array] backtrace The backtrace
|
|
46
|
+
# @param [String] message The exception message
|
|
47
|
+
# @param [String] full_message The full exception message
|
|
48
|
+
# @return [Eyeloupe::Exception] The exception model
|
|
49
|
+
def create_or_update_exception(kind, file, line, backtrace, message, full_message)
|
|
50
|
+
obj = Eyeloupe::Exception.find_by(kind: kind, file: file, line: line)
|
|
51
|
+
|
|
52
|
+
if obj
|
|
53
|
+
obj.update(count: obj.count + 1, updated_at: Time.now)
|
|
54
|
+
else
|
|
55
|
+
obj = Eyeloupe::Exception.create(
|
|
56
|
+
hostname: Socket.gethostname,
|
|
57
|
+
kind: kind,
|
|
58
|
+
message: message,
|
|
59
|
+
full_message: full_message,
|
|
60
|
+
location: read_file(backtrace || []).to_json,
|
|
61
|
+
file: file,
|
|
62
|
+
line: line,
|
|
63
|
+
stacktrace: (backtrace || []).to_json,
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
obj
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -31,6 +31,9 @@ module Eyeloupe
|
|
|
31
31
|
# @return [Array]
|
|
32
32
|
attr_accessor :subs
|
|
33
33
|
|
|
34
|
+
# @return [Eyeloupe::Exception, nil]
|
|
35
|
+
attr_accessor :ex
|
|
36
|
+
|
|
34
37
|
def initialize
|
|
35
38
|
@env = {}
|
|
36
39
|
@request = ActionDispatch::Request.new(@env)
|
|
@@ -40,6 +43,7 @@ module Eyeloupe
|
|
|
40
43
|
@timings = {}
|
|
41
44
|
@started_at = nil
|
|
42
45
|
@subs = []
|
|
46
|
+
@ex = nil
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
# @param [ActionDispatch::Request] request The request object
|
|
@@ -47,8 +51,9 @@ module Eyeloupe
|
|
|
47
51
|
# @param [Integer, nil] status HTTP status code
|
|
48
52
|
# @param [Hash, nil] headers HTTP headers
|
|
49
53
|
# @param [String, nil] response HTTP response
|
|
54
|
+
# @param [Eyeloupe::Exception, nil] ex The exception object persisted in db
|
|
50
55
|
# @return [Eyeloupe::Processors::InRequest]
|
|
51
|
-
def init(request, env, status, headers, response)
|
|
56
|
+
def init(request, env, status, headers, response, ex)
|
|
52
57
|
unsubscribe
|
|
53
58
|
|
|
54
59
|
@request = request
|
|
@@ -56,6 +61,7 @@ module Eyeloupe
|
|
|
56
61
|
@status = status
|
|
57
62
|
@headers = headers
|
|
58
63
|
@response = response
|
|
64
|
+
@ex = ex
|
|
59
65
|
|
|
60
66
|
self
|
|
61
67
|
end
|
|
@@ -72,7 +78,8 @@ module Eyeloupe
|
|
|
72
78
|
|
|
73
79
|
# @return [Eyeloupe::InRequest]
|
|
74
80
|
def process
|
|
75
|
-
|
|
81
|
+
|
|
82
|
+
req = Eyeloupe::InRequest.create(
|
|
76
83
|
verb: @request.request_method,
|
|
77
84
|
hostname: @request.host,
|
|
78
85
|
path: @env["REQUEST_URI"],
|
|
@@ -88,6 +95,10 @@ module Eyeloupe
|
|
|
88
95
|
session: (@request.session || {}).to_json,
|
|
89
96
|
response: get_response,
|
|
90
97
|
)
|
|
98
|
+
|
|
99
|
+
@ex.update(in_request_id: req.id) if @ex.present? && @ex.in_request_id.blank?
|
|
100
|
+
|
|
101
|
+
req
|
|
91
102
|
end
|
|
92
103
|
|
|
93
104
|
protected
|
|
@@ -105,7 +116,7 @@ module Eyeloupe
|
|
|
105
116
|
|
|
106
117
|
# @return [String, nil]
|
|
107
118
|
def get_response
|
|
108
|
-
if @request.format.to_s =~ /html/
|
|
119
|
+
if @request.format.to_s =~ /html/ || @headers&.to_json =~ /html/
|
|
109
120
|
"HTML content"
|
|
110
121
|
elsif @response.is_a?(ActionDispatch::Response)
|
|
111
122
|
@response.body
|
|
@@ -11,25 +11,13 @@ module Eyeloupe
|
|
|
11
11
|
# @return [String]
|
|
12
12
|
attr_accessor :body
|
|
13
13
|
|
|
14
|
-
# @return [Hash]
|
|
15
|
-
attr_accessor :req_headers
|
|
16
|
-
|
|
17
|
-
# @return [Hash]
|
|
18
|
-
attr_accessor :res_headers
|
|
19
|
-
|
|
20
|
-
# @return [Net::HTTPResponse, nil]
|
|
21
|
-
attr_accessor :response
|
|
22
|
-
|
|
23
14
|
# @return [Time, nil]
|
|
24
15
|
attr_accessor :started_at
|
|
25
16
|
|
|
26
17
|
def initialize
|
|
27
18
|
@request = nil
|
|
28
19
|
@body = ""
|
|
29
|
-
@req_headers = {}
|
|
30
|
-
@res_headers = {}
|
|
31
20
|
@started_at = nil
|
|
32
|
-
@response = nil
|
|
33
21
|
end
|
|
34
22
|
|
|
35
23
|
# @param [Net::HTTPRequest] request The request object
|
|
@@ -41,22 +29,24 @@ module Eyeloupe
|
|
|
41
29
|
end
|
|
42
30
|
|
|
43
31
|
# @param [Net::HTTPResponse] response The response object
|
|
32
|
+
# @param [Eyeloupe::Exception, nil] ex The exception object persisted in db
|
|
44
33
|
# @return [Net::HTTPResponse] The response object
|
|
45
|
-
def process(response)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Eyeloupe::OutRequest.create(
|
|
34
|
+
def process(response, ex)
|
|
35
|
+
req = Eyeloupe::OutRequest.create(
|
|
49
36
|
verb: @request.method,
|
|
50
37
|
hostname: @request['host'],
|
|
51
38
|
path: @request.path,
|
|
52
|
-
status:
|
|
53
|
-
format:
|
|
39
|
+
status: response.code,
|
|
40
|
+
format: response.content_type,
|
|
54
41
|
duration: (Time.now - @started_at) * 1000,
|
|
55
42
|
payload: @request.body,
|
|
56
43
|
req_headers: (get_headers(@request) || {}).to_json,
|
|
57
|
-
res_headers: (get_headers(
|
|
58
|
-
response:
|
|
44
|
+
res_headers: (get_headers(response) || {}).to_json,
|
|
45
|
+
response: response.body,
|
|
59
46
|
)
|
|
47
|
+
|
|
48
|
+
ex.update(out_request_id: req.id) if ex.present? && ex.out_request_id.blank?
|
|
49
|
+
|
|
60
50
|
response
|
|
61
51
|
end
|
|
62
52
|
|
|
@@ -5,30 +5,42 @@ module Eyeloupe
|
|
|
5
5
|
# @return [Eyeloupe::Processors::InRequest]
|
|
6
6
|
attr_accessor :inrequest_processor
|
|
7
7
|
|
|
8
|
+
# @return [Eyeloupe::Processors::Exception]
|
|
9
|
+
attr_accessor :exception_processor
|
|
10
|
+
|
|
8
11
|
def initialize(app)
|
|
9
12
|
@app = app
|
|
10
13
|
@inrequest_processor = Processors::InRequest.instance
|
|
14
|
+
@exception_processor = Processors::Exception.instance
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
# @param [Hash] env Rack environment
|
|
14
18
|
def call(env)
|
|
15
19
|
|
|
16
20
|
request = ActionDispatch::Request.new(env)
|
|
21
|
+
ex = nil
|
|
17
22
|
|
|
18
23
|
if enabled?(request) && !skip_request?(request)
|
|
19
24
|
@inrequest_processor.start_timer
|
|
20
25
|
|
|
21
26
|
begin
|
|
22
27
|
status, headers, response = @app.call(env)
|
|
28
|
+
|
|
29
|
+
framework_exception = env['action_dispatch.exception']
|
|
30
|
+
if framework_exception
|
|
31
|
+
ex = @exception_processor.process(env, framework_exception)
|
|
32
|
+
end
|
|
33
|
+
|
|
23
34
|
[status, headers, response]
|
|
24
35
|
rescue Exception => e
|
|
25
36
|
exception = ActionDispatch::ExceptionWrapper.new(env, e)
|
|
26
37
|
status = exception.status_code
|
|
27
38
|
headers = {}
|
|
28
39
|
response = e.message
|
|
40
|
+
ex = @exception_processor.process(env, e)
|
|
29
41
|
raise
|
|
30
42
|
ensure
|
|
31
|
-
@inrequest_processor.init(request, env, status, headers, response).process
|
|
43
|
+
@inrequest_processor.init(request, env, status, headers, response, ex).process
|
|
32
44
|
end
|
|
33
45
|
else
|
|
34
46
|
@app.call(env)
|
data/lib/eyeloupe/version.rb
CHANGED
data/lib/eyeloupe.rb
CHANGED
|
@@ -3,11 +3,13 @@ require "eyeloupe/engine"
|
|
|
3
3
|
|
|
4
4
|
require 'eyeloupe/request_middleware'
|
|
5
5
|
require 'eyeloupe/configuration'
|
|
6
|
-
require 'eyeloupe/http'
|
|
7
6
|
require 'eyeloupe/processors/in_request'
|
|
8
7
|
require 'eyeloupe/processors/out_request'
|
|
8
|
+
require 'eyeloupe/processors/exception'
|
|
9
|
+
require 'eyeloupe/concerns/rescuable'
|
|
9
10
|
|
|
10
11
|
require 'pagy'
|
|
12
|
+
require "openai"
|
|
11
13
|
module Eyeloupe
|
|
12
14
|
|
|
13
15
|
# @return [Eyeloupe::Configuration]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eyeloupe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexandre Lion
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-06-
|
|
11
|
+
date: 2023-06-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sprockets-rails
|
|
@@ -66,6 +66,20 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '6.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: ruby-openai
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 4.1.0
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 4.1.0
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
84
|
name: sqlite3
|
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -125,6 +139,7 @@ files:
|
|
|
125
139
|
- app/assets/images/eyeloupe/logo.png
|
|
126
140
|
- app/assets/javascripts/eyeloupe/application.js
|
|
127
141
|
- app/assets/javascripts/eyeloupe/controllers/application.js
|
|
142
|
+
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/ai_assistant_controller.js
|
|
128
143
|
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/nav_controller.js
|
|
129
144
|
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/pause_controller.js
|
|
130
145
|
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/refresh_controller.js
|
|
@@ -133,17 +148,23 @@ files:
|
|
|
133
148
|
- app/assets/stylesheets/application.tailwind.css
|
|
134
149
|
- app/assets/stylesheets/eyeloupe/application.css
|
|
135
150
|
- app/controllers/concerns/eyeloupe/searchable.rb
|
|
151
|
+
- app/controllers/eyeloupe/ai_assistant_responses_controller.rb
|
|
136
152
|
- app/controllers/eyeloupe/application_controller.rb
|
|
137
153
|
- app/controllers/eyeloupe/configs_controller.rb
|
|
138
154
|
- app/controllers/eyeloupe/data_controller.rb
|
|
155
|
+
- app/controllers/eyeloupe/exceptions_controller.rb
|
|
139
156
|
- app/controllers/eyeloupe/in_requests_controller.rb
|
|
140
157
|
- app/controllers/eyeloupe/out_requests_controller.rb
|
|
141
158
|
- app/helpers/eyeloupe/application_helper.rb
|
|
142
159
|
- app/jobs/eyeloupe/application_job.rb
|
|
143
160
|
- app/mailers/eyeloupe/application_mailer.rb
|
|
144
161
|
- app/models/eyeloupe/application_record.rb
|
|
162
|
+
- app/models/eyeloupe/exception.rb
|
|
145
163
|
- app/models/eyeloupe/in_request.rb
|
|
146
164
|
- app/models/eyeloupe/out_request.rb
|
|
165
|
+
- app/views/eyeloupe/exceptions/_frame.html.erb
|
|
166
|
+
- app/views/eyeloupe/exceptions/index.html.erb
|
|
167
|
+
- app/views/eyeloupe/exceptions/show.html.erb
|
|
147
168
|
- app/views/eyeloupe/in_requests/_frame.html.erb
|
|
148
169
|
- app/views/eyeloupe/in_requests/index.html.erb
|
|
149
170
|
- app/views/eyeloupe/in_requests/show.html.erb
|
|
@@ -158,10 +179,12 @@ files:
|
|
|
158
179
|
- config/tailwind.config.js
|
|
159
180
|
- db/migrate/20230518175305_create_eyeloupe_in_requests.rb
|
|
160
181
|
- db/migrate/20230525125352_create_eyeloupe_out_requests.rb
|
|
182
|
+
- db/migrate/20230604190442_create_eyeloupe_exceptions.rb
|
|
161
183
|
- lib/eyeloupe.rb
|
|
184
|
+
- lib/eyeloupe/concerns/rescuable.rb
|
|
162
185
|
- lib/eyeloupe/configuration.rb
|
|
163
186
|
- lib/eyeloupe/engine.rb
|
|
164
|
-
- lib/eyeloupe/
|
|
187
|
+
- lib/eyeloupe/processors/exception.rb
|
|
165
188
|
- lib/eyeloupe/processors/in_request.rb
|
|
166
189
|
- lib/eyeloupe/processors/out_request.rb
|
|
167
190
|
- lib/eyeloupe/request_middleware.rb
|
data/lib/eyeloupe/http.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require 'net/http'
|
|
3
|
-
module Net
|
|
4
|
-
class HTTP
|
|
5
|
-
alias original_request request
|
|
6
|
-
|
|
7
|
-
def request(req, body = nil, &block)
|
|
8
|
-
if Eyeloupe.configuration.capture
|
|
9
|
-
Eyeloupe::Processors::OutRequest.instance.init(req, body)
|
|
10
|
-
res = original_request(req, body, &block)
|
|
11
|
-
Eyeloupe::Processors::OutRequest.instance.process(res)
|
|
12
|
-
else
|
|
13
|
-
res = original_request(req, body, &block)
|
|
14
|
-
end
|
|
15
|
-
res
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
end
|