ask_chatgpt 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66ad01c3932f32f34589d4d2eaf742c28dcd607543d98ec886e3fe56da3c655b
4
- data.tar.gz: 1a7609890b8fc4ea6249ea2b563ab29ebfca0528f4255ffc4e00a3f1db0b806f
3
+ metadata.gz: bac19fcd064226b0e1b2b87ebb215547d8e49b59233fc6c6b17440ac4df751a0
4
+ data.tar.gz: 6a53484fdcdb4a872b768322a09b517807f62f17df5fc4ba3dbcfc133b65f4c7
5
5
  SHA512:
6
- metadata.gz: e07712c65e13c713271127dcf539a3007763c03fe5e8afa7523820b31a003e442414d22f4fc218fa82f2f9294c3dd2679ab7ef9fcc4070635e58c05e50db62f8
7
- data.tar.gz: a3564f0aecca1cea800486aa93758f46747d10cc5a185b66be43036dbc05797d92bc7f71566db652b191b1d9375790be5f3239f99f73c5f573105edcbeac23ca
6
+ metadata.gz: d8e51c4918ed071863f6ee2fc7ca559782e13394fd508c9422cd914ec9604eccb1b4d25455c4b3c30228dcc0caef7796d6b5eb2481c83f0c3669503ffe149a34
7
+ data.tar.gz: 791ecab6cfb6220b81e2943f78512e5b35c95720b212591d4bbb2c0367c9fe9090f8d368cc780c7d0951439c9480a3544126d06fe566fdc56d7af0bcd620e057
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # AskChatGPT
1
+ # Ask ChatGPT
2
2
 
3
3
  [![RailsJazz](https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/my_other.svg?raw=true)](https://www.railsjazz.com)
4
4
  [![Listed on OpenSource-Heroes.com](https://opensource-heroes.com/badge-v1.svg)](https://opensource-heroes.com/o/railsjazz)
@@ -9,7 +9,7 @@ AI-Powered Assistant Gem right in your Rails console.
9
9
 
10
10
  A Gem that leverages the power of AI to make your development experience more efficient and enjoyable. With this gem, you can streamline your coding process, effortlessly refactor and improve your code, and even generate tests on the fly.
11
11
 
12
- We are welcoming you to propose new prompts or adjust existing ones!
12
+ See more [examples](#examples) below.
13
13
 
14
14
  ## Usage
15
15
 
@@ -18,16 +18,28 @@ Go to Rails console and run:
18
18
  ```ruby
19
19
  gpt.ask("how to get max age of user with projects from Ukraine").with_model(User, Project, Country)
20
20
  gpt.ask("convert json to xml")
21
+ gpt.payload(json).ask("extract emails from json")
21
22
  gpt.refactor("User.get_report")
22
23
  gpt.improve("User.get_report")
23
24
  gpt.rspec_test(User)
24
25
  gpt.unit_test(User)
25
26
  gpt.code_review(User.method(:get_report))
26
- gpt.find_bug('User.full_name')
27
+ gpt.find_bug('User#full_name')
28
+ gpt.explain(User)
29
+ gpt.improve %Q{
30
+ def full_name
31
+ [first_name, last_name].join
32
+ end
33
+ }
27
34
  ```
28
35
 
36
+ See some examples below. You can also create your own prompts with just few lines of code [here](#options--configurations).
37
+
29
38
  ## Examples
30
39
 
40
+ Typical use-cases how you can use this plugin
41
+ ![AskChatGPT](docs/gpt6.png)
42
+
31
43
  Ask for code ideas:
32
44
  ![AskChatGPT](docs/gpt1.png)
33
45
 
@@ -40,6 +52,15 @@ What about unit tests?
40
52
  Ask ChatGPT to improve your code:
41
53
  ![AskChatGPT](docs/gpt4.png)
42
54
 
55
+ Ask ChatGPT to parse you JSON:
56
+ ![AskChatGPT](docs/gpt7.png)
57
+
58
+ Or it can encode in Base64 your string:
59
+ ![AskChatGPT](docs/gpt8.png)
60
+
61
+ Create I18n YAML for your Model (custom prompt):
62
+ ![AskChatGPT](docs/gpt5.png)
63
+
43
64
  ## Installation
44
65
 
45
66
  Add this line to your application's Gemfile:
@@ -58,7 +79,7 @@ Or install it yourself as:
58
79
  $ gem install ask_chatgpt
59
80
  ```
60
81
 
61
- ## Options
82
+ ## Options & Configurations
62
83
 
63
84
  Run `rails g ask_chatgpt initializer`.
64
85
 
@@ -66,25 +87,83 @@ And you can edit:
66
87
 
67
88
  ```ruby
68
89
  AskChatGPT.setup do |config|
69
- # config.access_token = ENV["OPENAI_API_KEY"]
70
- # config.debug = false
71
- # config.model = "gpt-3.5-turbo"
72
- # config.temperature = 0.1
90
+ # config.access_token = ENV["OPENAI_API_KEY"]
91
+ # config.debug = false
92
+ # config.model = "gpt-3.5-turbo"
93
+ # config.temperature = 0.1
94
+ # config.max_tokens = 3000 # or nil by default
95
+ # config.included_prompt = []
96
+
97
+ # Examples of custom prompts:
98
+ # you can use them `gpt.ask(:extract_email, "some string")`
99
+
100
+ # config.register_prompt :extract_email do |arg|
101
+ # "Extract email from: #{arg} as JSON"
102
+ # end
103
+
104
+ # config.register_prompt :extract_constants do |arg|
105
+ # "Extract constants from class: #{AskChatGPT::Helpers.extract_source(arg)}"
106
+ # end
107
+
108
+ # config.register_prompt :i18n do |code|
109
+ # "Use I18n in this code:\n#{AskChatGPT::Helpers.extract_source(code)}"
110
+ # end
73
111
  end
74
112
  ```
75
113
 
114
+ Note: that you need to setup your API Key https://platform.openai.com/account/api-keys. You can store it in the .env or .bash_profile. BUT make sure it won't be committed to the Github. Is must be private.
115
+
116
+ You can define you own prompts and use them using `.register_prompt`. For example:
117
+
118
+ ```ruby
119
+ config.register_prompt :extract_email do |arg|
120
+ "Extract email from: #{arg} as JSON"
121
+ end
122
+ ```
123
+
124
+ And later you can call it with `gpt.extract_email("some text with email@site.com, user@email.com")`.
125
+ If you believe your custom promts will be useful - create a PR for this gem.
126
+
127
+ If you want to get source code use this helper `AskChatGPT::Helpers.extract_source(str)`.
128
+
129
+ You can pass:
130
+
131
+ ```ruby
132
+ AskChatGPT::Helpers.extract_source('User.some_class_method')
133
+ AskChatGPT::Helpers.extract_source('User#instance_method')
134
+ AskChatGPT::Helpers.extract_source('User')
135
+ AskChatGPT::Helpers.extract_source(User)
136
+ AskChatGPT::Helpers.extract_source("a = b")
137
+ ```
138
+
139
+ ## Debug Mode
140
+
141
+ You can enable debug mode to see request/response from the OpenAI using two ways:
142
+
143
+ ```ruby
144
+ AskChatGPT.setup do |config|
145
+ config.debug = false
146
+ end
147
+ ```
148
+
149
+ or directly in console `gpt.debug!` (and finish `gpt.debug!(:off)`)
150
+
76
151
  ## TODO
77
152
 
78
- - cli app?
79
- - more prompts (cover controllers, sql, etc?)
80
- - tests
153
+ - cli app? `ask_gpt <something> --file <file>` ...
154
+ - more prompts (cover controllers, sql, etc?), e.g. `with_controller`, `with_class`, ...
155
+ - tests(rspec, vcr)
156
+ - CI (but first specs)
81
157
  - can it be used with pry/byebug/etc?
82
- - print tokens usage?
83
- - support org_id?
158
+ - print tokens usage? `.with_usage`
159
+ - support org_id? in the configs
160
+ - use `gpt` in the code of the main app (e.g. model/controller)
84
161
 
85
162
  ## Contributing
86
163
 
87
- You are welcome to contribute.
164
+ We are welcoming you to propose new prompts or adjust existing ones!
165
+
166
+ To start just clone this repo. Run bundle, and go to `cd test/dummy/`. Start `rails c` and test your prompts.
88
167
 
89
168
  ## License
90
169
 
@@ -0,0 +1,14 @@
1
+ module AskChatgpt
2
+ module Console
3
+ def gpt
4
+ AskChatGPT::Core.call
5
+ end
6
+
7
+ alias :chatgpt :gpt
8
+ alias :chat_gpt :gpt
9
+
10
+ def run(*args)
11
+ gpt(*args)
12
+ end
13
+ end
14
+ end
@@ -9,62 +9,84 @@ end
9
9
 
10
10
  module AskChatgpt
11
11
  class Executor
12
+ DEFAULT_PROMPTS = [:improve, :refactor, :question, :find_bug, :code_review, :rspec_test, :unit_test, :explain]
13
+
12
14
  attr_reader :scope, :client
13
15
 
14
16
  def initialize(client)
15
- @scope = [AskChatGPT::Prompts::App.new]
17
+ @scope = AskChatGPT.included_prompt.dup
16
18
  @client = client
17
19
  end
18
20
 
21
+ def debug!(mode = :on)
22
+ AskChatGPT.debug = mode == :on
23
+ end
24
+
19
25
  def with_model(*models)
20
- models.each do |model|
21
- @scope << AskChatGPT::Prompts::Model.new(model)
26
+ self.tap do
27
+ models.each do |model|
28
+ add_prompt AskChatGPT::Prompts::Model.new(model)
29
+ end
22
30
  end
23
- self
24
31
  end
25
-
26
32
  alias :with_models :with_model
27
33
 
28
- [:improve, :refactor, :question, :find_bug, :code_review, :rspec_test, :unit_test].each do |method|
34
+ DEFAULT_PROMPTS.each do |method|
29
35
  define_method(method) do |*args|
30
- @scope << AskChatGPT::Prompts.const_get(method.to_s.camelize).new(*args)
31
- self
36
+ add_prompt(AskChatGPT::Prompts.const_get(method.to_s.camelize).new(*args))
32
37
  end
33
38
  end
34
-
35
39
  alias :ask :question
40
+ alias :payload :question
36
41
  alias :how :question
37
42
  alias :find :question
38
43
  alias :review :code_review
39
44
 
40
45
  def inspect
46
+ pp(executor_parameters) if AskChatGPT.debug
41
47
  puts(call); nil
48
+ rescue StandardError => e
49
+ puts e.message
50
+ puts e.backtrace.take(5).join("\n")
51
+ nil
42
52
  end
43
53
 
44
54
  def call
45
- pp parameters if AskChatGPT.debug
46
-
47
- return puts("No prompts given") if scope.size == 1 # only App prompt
55
+ if scope.empty? || (scope.size == 1 && scope.first.is_a?(AskChatGPT::Prompts::App))
56
+ return puts("No prompts given")
57
+ end
48
58
 
49
59
  spinner = TTY::Spinner.new(format: :classic)
50
60
  spinner.auto_spin
51
-
52
- response = client.chat(parameters: parameters)
53
- content = response.dig("choices", 0, "message", "content")
61
+ response = client.chat(parameters: executor_parameters)
54
62
  spinner.stop
55
63
 
56
- parsed = TTY::Markdown.parse(content)
57
- parsed
64
+ pp(response) if AskChatGPT.debug
65
+
66
+ if response["error"]
67
+ puts response["error"]["message"]
68
+ else
69
+ content = response.dig("choices", 0, "message", "content")
70
+ parsed = TTY::Markdown.parse(content)
71
+ parsed
72
+ end
58
73
  ensure
59
74
  spinner.stop if spinner.spinning?
60
75
  end
61
76
 
62
- def parameters
63
- {
77
+ def executor_parameters
78
+ @executor_parameters ||= {
64
79
  model: AskChatGPT.model,
65
- messages: scope.map { |e| { role: "user", content: e.content } },
66
80
  temperature: AskChatGPT.temperature,
67
- }
81
+ max_tokens: AskChatGPT.max_tokens,
82
+ messages: scope.map { |e| { role: "user", content: e.content } }.reject { |e| e[:content].blank? },
83
+ }.compact_blank
68
84
  end
85
+
86
+ def add_prompt(prompt)
87
+ @scope << prompt
88
+ self
89
+ end
90
+
69
91
  end
70
92
  end
@@ -0,0 +1,49 @@
1
+ module AskChatgpt
2
+ class Helpers
3
+ class << self
4
+ def extract_source(method_or_class_or_str)
5
+ case method_or_class_or_str
6
+ when ::Proc, ::Method, ::UnboundMethod
7
+ method_or_class_or_str.source
8
+ when Module, Class, String
9
+ str = capture_io do
10
+ IRB.CurrentContext.main.irb_show_source(method_or_class_or_str.to_s)
11
+ end.join("\n")
12
+ # check if source was extracted
13
+ if str.include?("locate a definition for")
14
+ method_or_class_or_str.to_s
15
+ else
16
+ str
17
+ end
18
+ else
19
+ raise "Not supported parameter"
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # from https://github.com/minitest/minitest/blob/master/lib/minitest/assertions.rb#L542
26
+ def capture_io
27
+ _synchronize do
28
+ begin
29
+ captured_stdout, captured_stderr = StringIO.new, StringIO.new
30
+
31
+ orig_stdout, orig_stderr = $stdout, $stderr
32
+ $stdout, $stderr = captured_stdout, captured_stderr
33
+
34
+ yield
35
+
36
+ return captured_stdout.string, captured_stderr.string
37
+ ensure
38
+ $stdout = orig_stdout
39
+ $stderr = orig_stderr
40
+ end
41
+ end
42
+ end
43
+
44
+ def _synchronize
45
+ yield
46
+ end
47
+ end
48
+ end
49
+ end
@@ -16,12 +16,12 @@ module AskChatgpt
16
16
  end
17
17
 
18
18
  def database_info
19
- db_name = ActiveRecord::Base.connection_db_config.configuration_hash[:database] rescue nil
19
+ db_name = ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] rescue nil
20
20
  "Database: #{db_name}" if db_name
21
21
  end
22
22
 
23
23
  def version_info
24
- "Version #{Rails.version}, Ruby Version: #{RUBY_VERSION}"
24
+ "Rails Version #{Rails.version}, Ruby Version: #{RUBY_VERSION}"
25
25
  end
26
26
  end
27
27
  end
@@ -1,14 +1,14 @@
1
1
  module AskChatgpt
2
2
  module Prompts
3
3
  class Base
4
- attr_reader :str
4
+ attr_reader :record
5
5
 
6
- def initialize(str = nil)
7
- @str = str
6
+ def initialize(record = nil)
7
+ @record = record
8
8
  end
9
9
 
10
10
  def content
11
- str
11
+ record
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,16 @@
1
+ module AskChatgpt
2
+ module Prompts
3
+ class Custom < Base
4
+ attr_reader :args, :block
5
+
6
+ def initialize(*args, block)
7
+ @args = args
8
+ @block = block
9
+ end
10
+
11
+ def content
12
+ instance_exec(*args, &block)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module AskChatgpt
2
+ module Prompts
3
+ class Explain < Improve
4
+ private def action_info
5
+ "Explain me this Ruby code snippet:"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,11 +1,6 @@
1
1
  module AskChatgpt
2
2
  module Prompts
3
3
  class Improve < Base
4
- attr_reader :method
5
-
6
- def initialize(method)
7
- @method = method
8
- end
9
4
 
10
5
  def content
11
6
  [
@@ -21,44 +16,7 @@ module AskChatgpt
21
16
  end
22
17
 
23
18
  def method_info
24
- case method
25
- when ::Proc, ::Method, ::UnboundMethod
26
- "Method source\n: #{method.source}"
27
- when Module, Class, String
28
- str = capture_io do
29
- IRB.CurrentContext.main.irb_show_source(method.to_s)
30
- end.join("\n")
31
- if str.include?("locate a definition for")
32
- method.to_s
33
- else
34
- str
35
- end
36
- else
37
- raise "Not supported parameter, pass a Method object (example: method(:foo)))"
38
- end
39
- end
40
-
41
- # from https://github.com/minitest/minitest/blob/master/lib/minitest/assertions.rb#L542
42
- def capture_io
43
- _synchronize do
44
- begin
45
- captured_stdout, captured_stderr = StringIO.new, StringIO.new
46
-
47
- orig_stdout, orig_stderr = $stdout, $stderr
48
- $stdout, $stderr = captured_stdout, captured_stderr
49
-
50
- yield
51
-
52
- return captured_stdout.string, captured_stderr.string
53
- ensure
54
- $stdout = orig_stdout
55
- $stderr = orig_stderr
56
- end
57
- end
58
- end
59
-
60
- def _synchronize
61
- yield
19
+ AskChatGPT::Helpers.extract_source(record)
62
20
  end
63
21
 
64
22
  end
@@ -1,12 +1,6 @@
1
1
  module AskChatgpt
2
2
  module Prompts
3
3
  class Model < Base
4
- attr_reader :model
5
-
6
- def initialize(model)
7
- @model = model
8
- end
9
-
10
4
  def content
11
5
  [
12
6
  summary_info,
@@ -19,19 +13,19 @@ module AskChatgpt
19
13
  private
20
14
 
21
15
  def summary_info
22
- "Model: #{model.name}, table name: #{model.table_name}"
16
+ "Active Record Model: #{record.name}, table name: #{record.table_name}"
23
17
  end
24
18
 
25
19
  def schema_info
26
- "Schema: #{model.table_name}\n" +
27
- model.columns.map do |column|
20
+ "Schema: #{record.table_name}\n" +
21
+ record.columns.map do |column|
28
22
  "#{column.name}: #{column.type}"
29
23
  end.join("\n")
30
24
  end
31
25
 
32
26
  def associations_info
33
- "#{model.name} Associations and Relations: \n" +
34
- model.reflections.map do |name, reflection|
27
+ "#{record.name} Associations and Relations: \n" +
28
+ record.reflections.map do |name, reflection|
35
29
  [
36
30
  "#{reflection.macro} :#{name}",
37
31
  reflection.options.inject("") { |h, (k, v)| h += "#{k}: #{v}" }
@@ -1,6 +1,9 @@
1
1
  module AskChatgpt
2
2
  module Prompts
3
3
  class Question < Base
4
+ def initialize(record)
5
+ @record = record.is_a?(String) ? record : record.to_json
6
+ end
4
7
  end
5
8
  end
6
9
  end
@@ -1,7 +1,7 @@
1
1
  module AskChatgpt
2
2
  class Railtie < ::Rails::Railtie
3
3
  console do
4
- TOPLEVEL_BINDING.eval('self').extend AskChatGPT::ConsoleMethods
4
+ TOPLEVEL_BINDING.eval('self').extend AskChatGPT::Console
5
5
  end
6
6
  end
7
7
  end
@@ -1,3 +1,3 @@
1
1
  module AskChatgpt
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/ask_chatgpt.rb CHANGED
@@ -6,10 +6,14 @@ require "openai"
6
6
  require "tty-markdown"
7
7
  require "tty-spinner"
8
8
 
9
+ require_relative "ask_chatgpt/console"
9
10
  require_relative "ask_chatgpt/executor"
11
+ require_relative "ask_chatgpt/helpers"
10
12
  require_relative "ask_chatgpt/core"
11
13
 
12
14
  module AskChatgpt
15
+ ::AskChatGPT = AskChatgpt
16
+
13
17
  mattr_accessor :debug
14
18
  @@debug = false
15
19
 
@@ -21,28 +25,32 @@ module AskChatgpt
21
25
  mattr_accessor :temperature
22
26
  @@temperature = 0.1
23
27
 
28
+ # default max tokens will be defined by the model
29
+ mattr_accessor :max_tokens
30
+ @@max_tokens = nil
31
+
24
32
  # use your own API key (local per set in initializer or ENV)
25
33
  mattr_accessor :access_token
26
34
  @@access_token = ENV["OPENAI_API_KEY"]
27
35
 
36
+ # this prompt is always included
37
+ # it constain info that you have Rails app and Rails/Ruby versions, and DB adapter name
38
+ mattr_accessor :included_prompt
39
+ @@included_prompt = [AskChatGPT::Prompts::App.new]
40
+
28
41
  def self.setup
29
42
  yield(self)
30
43
  end
31
44
 
32
- module ConsoleMethods
33
- def gpt
34
- AskChatGPT::Core.call
35
- end
36
-
37
- alias :chatgpt :gpt
38
- alias :chat_gpt :gpt
39
-
40
- def run(*args)
41
- gpt(*args)
45
+ def self.register_prompt(name, &block)
46
+ # i want to create a module and include it into a class, with method name and code from block
47
+ AskChatGPT::Executor.class_eval do
48
+ define_method(name) do |*args|
49
+ @scope << AskChatGPT::Prompts::Custom.new(*args, block)
50
+ self
51
+ end
42
52
  end
43
53
  end
44
54
 
45
- extend ConsoleMethods
55
+ extend AskChatGPT::Console
46
56
  end
47
-
48
- AskChatGPT = AskChatgpt
@@ -1,6 +1,23 @@
1
1
  AskChatGPT.setup do |config|
2
- # config.access_token = ENV["OPENAI_API_KEY"]
3
- # config.debug = false
4
- # config.model = "gpt-3.5-turbo"
5
- # config.temperature = 0.1
2
+ # config.access_token = ENV["OPENAI_API_KEY"]
3
+ # config.debug = false
4
+ # config.model = "gpt-3.5-turbo"
5
+ # config.max_tokens = 3000 # or nil by default
6
+ # config.temperature = 0.1
7
+ # config.included_prompt = []
8
+
9
+ # Examples of custom prompts:
10
+ # you can use them `gpt.ask(:extract_email, "some string")`
11
+
12
+ # config.register_prompt :extract_email do |arg|
13
+ # "Extract email from: #{arg} as JSON"
14
+ # end
15
+
16
+ # config.register_prompt :extract_constants do |arg|
17
+ # "Extract constants from class: #{AskChatGPT::Helpers.extract_source(arg)}"
18
+ # end
19
+
20
+ # config.register_prompt :i18n do |code|
21
+ # "Use I18n in this code:\n#{AskChatGPT::Helpers.extract_source(code)}"
22
+ # end
6
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask_chatgpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-04-22 00:00:00.000000000 Z
12
+ date: 2023-04-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -107,11 +107,15 @@ files:
107
107
  - README.md
108
108
  - Rakefile
109
109
  - lib/ask_chatgpt.rb
110
+ - lib/ask_chatgpt/console.rb
110
111
  - lib/ask_chatgpt/core.rb
111
112
  - lib/ask_chatgpt/executor.rb
113
+ - lib/ask_chatgpt/helpers.rb
112
114
  - lib/ask_chatgpt/prompts/app.rb
113
115
  - lib/ask_chatgpt/prompts/base.rb
114
116
  - lib/ask_chatgpt/prompts/code_review.rb
117
+ - lib/ask_chatgpt/prompts/custom.rb
118
+ - lib/ask_chatgpt/prompts/explain.rb
115
119
  - lib/ask_chatgpt/prompts/find_bug.rb
116
120
  - lib/ask_chatgpt/prompts/improve.rb
117
121
  - lib/ask_chatgpt/prompts/model.rb