ask-ai 0.0.5 → 1.0.1

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: f43705932c791d2b495230b8ac87567a23dafddd7c76fbbdc9cd0d88d960aac7
4
- data.tar.gz: c8376477e6a4261f04ced1c99ccc07d433551e1613c317b51ab1b21b53e50b58
3
+ metadata.gz: e73628fad8025e8de63e07c4256f42426eb9651fb62a418f8f73c6b7b4db432d
4
+ data.tar.gz: 4575f5535ddedd005e846cf598c3f464717130b2ce065b7c29e2e174cadebd30
5
5
  SHA512:
6
- metadata.gz: 26841ece9e0ee1cb05d1df0d6cbf1b20db94d18daf74be4decf82599bf3614f5a6823a2ae3398a26d9a4df480c9eedb51285934bd674d4f3b2eac5355b617da1
7
- data.tar.gz: 0ecfbc09c3944f62ef1cfdc3ddbef975d7b2cf3ea69913bff416ba25f6df063436bdbf7359bf40ca9b80fa4ce0d4dfc74d54d79b487f1f8c9b22365a2027c70e
6
+ metadata.gz: a2fccb29fba0174e10556377b2422fb6d3de99eef58820af2289ef77dd0379029c5d989d745eb388ef0901c153de2ce22163a9fb8b11d1f133d84e0d3bdb6b62
7
+ data.tar.gz: 45bc0ea0ef5e78a465b196fe262a2b1b88cdeb2bd9b15af86300d72ac2cf220c4b1ebeb860b41782e02bad51dc87d34e32a7522c1ff3129bc1902995c0dfcebf
data/config/config.yml CHANGED
@@ -1,3 +0,0 @@
1
- ---
2
- TEMPERATURE: 0.7
3
- CONTEXT_LENGTH: 12
data/lib/config.rb CHANGED
@@ -1,65 +1,110 @@
1
1
  require_relative './files.rb'
2
2
 
3
3
  module Config
4
- include Files
5
- def self.load_key()
6
- config = YAML.load_file(Files.config_path)
4
+ extend Files
5
+ def load_key()
6
+ config = YAML.load_file(config_path)
7
7
  config['OPENAI_API_KEY']
8
8
  end
9
9
 
10
- def self.save_key(api_key)
11
- config = YAML.load_file(Files.config_path)
10
+ def save_key(api_key)
11
+ config = YAML.load_file(config_path)
12
+ if (config == false || config.nil?)
13
+ config = {}
14
+ end
12
15
  config['OPENAI_API_KEY'] = api_key
13
- File.open(Files.config_path, 'w') { |f| YAML.dump(config, f) }
16
+ File.open(config_path, 'w') { |f| YAML.dump(config, f) }
14
17
  end
15
18
 
16
- def self.load_temperature
17
- config = YAML.load_file(Files.config_path)
18
- config['TEMPERATURE']
19
+ def load_temperature
20
+ config = YAML.load_file(config_path)
21
+ unless (config == false || config.nil?)
22
+ config['TEMPERATURE']
23
+ end
19
24
  end
20
25
 
21
- def self.save_temperature(temperature)
22
- config = YAML.load_file(Files.config_path)
26
+ def save_temperature(temperature)
27
+ config = YAML.load_file(config_path)
28
+ if config == false
29
+ config = {}
30
+ end
23
31
  config['TEMPERATURE'] = temperature.to_f
24
- File.open(Files.config_path, 'w') { |f| YAML.dump(config, f) }
32
+ File.open(config_path, 'w') { |f| YAML.dump(config, f) }
25
33
  end
26
34
 
27
- def self.load_context_length
28
- config = YAML.load_file(Files.config_path)
29
- config['CONTEXT_LENGTH']
35
+ def load_context_length
36
+ config = YAML.load_file(config_path)
37
+ unless (config == false || config.nil?)
38
+ config['CONTEXT_LENGTH']
39
+ end
30
40
  end
31
41
 
32
- def self.save_context_length(context_length)
33
- config = YAML.load_file(Files.config_path)
42
+ def save_context_length(context_length)
43
+ config = YAML.load_file(config_path)
44
+ if config == false
45
+ config = {}
46
+ end
34
47
  config['CONTEXT_LENGTH'] = context_length.to_i
35
- File.open(Files.config_path, 'w') { |f| YAML.dump(config, f) }
48
+ File.open(config_path, 'w') { |f| YAML.dump(config, f) }
36
49
  end
37
50
 
38
- def self.set_config(value)
39
- puts 'value',value
51
+ def set_config(value)
40
52
  if value.include?('key')
41
53
  stript_value = value.sub(/^key/, '').strip
42
54
  if stript_value.empty?
43
- puts 'No API key given'
44
- exit
55
+ return 'No API key given'
45
56
  end
46
57
  save_key(stript_value)
58
+ return 'API key saved'
47
59
  elsif value.include?('temp')
48
- stript_value = value.sub(/^temp/, '').strip
49
- if stript_value.empty?
50
- puts 'No temperature given'
51
- exit
60
+ stript_value = value.sub(/^temp/, '').strip.to_f
61
+ if stript_value.to_f > 1.0 || stript_value.to_f < 0.1
62
+ return 'Temperature must be between 0.1 and 1.0'
52
63
  end
53
64
  save_temperature(stript_value)
65
+ return 'Temperature saved'
54
66
  elsif value.include?('context')
55
- stript_value = value.sub(/^context/, '').strip
56
- if stript_value.empty?
57
- puts 'No context length given'
58
- exit
67
+ stript_value = value.sub(/^context/, '').strip.to_i
68
+ if stript_value.to_i > 100 || stript_value.to_i < 1
69
+ return 'Context length must be between 1 and 100'
59
70
  end
60
71
  save_context_length(stript_value)
72
+ return 'Context length saved'
61
73
  else
62
- puts 'Invalid config value'
74
+ return 'Invalid config value'
63
75
  end
64
76
  end
77
+
78
+ def set_key(api_key: nil)
79
+ if api_key.nil?
80
+ log("Setting API key...")
81
+ log("Enter API key: (or press enter to exit)")
82
+
83
+ while input = Readline.readline("> ", true) do
84
+ if input.empty?
85
+ log("Exiting.")
86
+ exit
87
+ else
88
+ api_key = input.strip
89
+ break
90
+ end
91
+ end
92
+ log("Saving API key...")
93
+ end
94
+
95
+ FileUtils.mkdir_p(File.dirname(config_path))
96
+ File.open(config_path, "w") do |f|
97
+ f.write(YAML.dump({ "OPENAI_API_KEY" => api_key }))
98
+ end
99
+ log("API key saved.")
100
+ log("")
101
+ end
102
+
103
+ def load_env()
104
+ #Config.load_key()
105
+ YAML.load(File.read(config_path))
106
+
107
+ rescue Errno::ENOENT
108
+ log("No config.yml found.")
109
+ end
65
110
  end
data/lib/context.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  require_relative './files.rb'
2
2
  require_relative './config.rb'
3
+ require_relative './file_format_error.rb'
3
4
  class Context
4
- include Files, Config
5
- def self.load_context()
6
- if File.exist?(Files.context_path)
7
- conversation = File.readlines(Files.context_path).map { |line| JSON.parse(line) }
5
+ extend Files, Config, Logging
6
+ def self.load_context(file_with_context: false)
7
+ if File.exist?(context_path)
8
+ conversation = File.readlines(context_path).map { |line| JSON.parse(line) }
8
9
  else
9
10
  conversation = []
10
11
  end
@@ -21,39 +22,50 @@ class Context
21
22
  end
22
23
  end
23
24
 
25
+ if file_with_context
26
+ return load_context_file() + context_as_string
27
+ end
28
+
24
29
  return context_as_string
25
30
  end
26
31
 
27
32
  ## Here we save the context to a file.
28
33
  ## Max 10 previous Q / A to save tokens.
29
34
  def self.save_context(context)
35
+ return if context.nil?
30
36
  tmp_arr = []
31
- unless File.exist?(Files.context_path)
32
- File.open(Files.context_path, "w") {}
37
+ unless File.exist?(context_path)
38
+ File.open(context_path, "w") {}
33
39
  end
34
- File.readlines(Files.context_path).map { |line| tmp_arr.push(JSON.parse(line)) }
35
- length = Config.load_context_length().nil? ? 10 : Config.load_context_length()
40
+ File.readlines(context_path).map { |line| tmp_arr.push(JSON.parse(line)) }
41
+ length = load_context_length().nil? ? 10 : load_context_length()
36
42
  if tmp_arr.length > length
37
43
  tmp_arr.shift()
38
44
  end
39
- File.truncate(Files.context_path, 0)
40
- tmp_arr.each { |line| File.open(Files.context_path, "a") { |file| file.write("#{line.to_json}\n") } }
41
- File.open(Files.context_path, "a") { |file| file.write("#{context.to_json}\n") }
45
+ File.truncate(context_path, 0)
46
+ tmp_arr.each { |line| File.open(context_path, "a") { |file| file.write("#{line.to_json}\n") } }
47
+ File.open(context_path, "a") { |file| file.write("#{context.to_json}\n") }
42
48
  end
43
49
 
44
50
  def self.delete_context()
45
- Logging.log("Deleting previous context.")
46
- File.truncate(Files.context_path, 0)
51
+ log("Deleting previous context.")
52
+ File.truncate(context_path, 0)
47
53
  end
48
54
 
49
55
  def self.save_context_file(file_path)
50
- puts "File path: #{file_path}"
56
+ ## If the file extenstion is pdf or docx raise an error.
57
+ ## This is not a complete list of file extensions.
58
+ if file_path.include?(".pdf") || file_path.include?(".docx")
59
+ raise FileFormatError, "File type not supported."
60
+ end
51
61
  unless file_path.nil?
62
+ conter = 0
52
63
  file_in = File.open(file_path, 'r')
53
- file_out = File.open(Files.context_file_path, 'w')
64
+ file_out = File.open(context_file_path, 'a')
65
+ file_out.write("loaded_context_file_path=#{file_path}\n")
54
66
  char_count = 0
55
67
  file_in.each do |line|
56
- puts "Line: #{line}"
68
+ #puts "Line: #{line}"
57
69
  char_count += line.length
58
70
  file_out.write(line)
59
71
  end
@@ -61,17 +73,19 @@ class Context
61
73
  file_out.close
62
74
 
63
75
  if char_count > 10000
64
- Logging.log("Warning: The file you are trying to feed to the API is #{char_count} characters long. This consumes a lot of tokens.")
76
+ log("Warning: The file you are trying to feed to the API is #{char_count} characters long. This consumes a lot of tokens.")
65
77
  end
66
78
  else
67
- Logging.log("No file path given.")
79
+ log("No file path given.")
68
80
  end
69
81
  rescue Errno::ENOENT
70
- Logging.log("No file at '#{file_path}' found.")
82
+ log("No file at '#{file_path}' found.")
83
+ rescue FileFormatError => e
84
+ log(e.message)
71
85
  end
72
86
 
73
87
  def self.load_context_file()
74
- file = File.open(Files.context_file_path, 'r')
88
+ file = File.open(context_file_path, 'r')
75
89
  file_as_string = ""
76
90
  file.each do |line|
77
91
  file_as_string += line
@@ -79,9 +93,74 @@ class Context
79
93
 
80
94
  return file_as_string
81
95
  rescue Errno::ENOENT
82
- Logging.log("No file at '#{Files.context_file_path}' found.")
83
- Logging.log("Load a file with 'aa -lf <file_path>'")
96
+ log("No file at '#{context_file_path}' found.")
97
+ log("Load a file with 'aa -lf <file_path>'")
84
98
  return ""
85
99
  end
100
+ ## This first class pasta method need to be refactored.
101
+ ## It's a mess.
102
+ def self.delete_file_context()
103
+ counter = 1
104
+ delete_lines = []
105
+ last_line = 0
106
+ File.open(context_file_path, "r") { |file|
107
+ if file.size == 0
108
+ log("No files loaded.")
109
+ return
110
+ end
111
+ file.readlines.each_with_index { |line, index|
112
+ if line.include?("loaded_context_file_path=")
113
+ log("#{counter}: #{line.gsub("loaded_context_file_path=", "")}")
114
+ delete_lines.push(index)
115
+ counter += 1
116
+ end
117
+ last_line = index
118
+ }
119
+ }
120
+ if counter == 1 + 1
121
+ log "One file loaded. Enter '1' or 'all' to delete it."
122
+ log "Enter 'a' to abort."
123
+ else
124
+ log("Which file do you want to delete? (1-#{counter - 1}) or 'all'")
125
+ log("Enter 'a' to abort.")
126
+ end
127
+
128
+ delete_counter = 0
129
+ while input = Readline.readline("\nDelete --> ", true) do
130
+ if input == "a"
131
+ log("Aborting.")
132
+ return
133
+ elsif input == "all"
134
+ File.truncate(context_file_path, 0)
135
+ log("Deleted all files.")
136
+ return
137
+ elsif input.to_i >= 1 && input.to_i < counter
138
+ lines_to_save = []
139
+ File.open(context_file_path, "r") { |file|
140
+ file.readlines.each_with_index { |line, index|
141
+ start_line = delete_lines[input.to_i - 1]
142
+ end_line = delete_lines[input.to_i]
143
+ if end_line.nil?
144
+ end_line = last_line + 1
145
+ end
146
+ if index < start_line || index > end_line - 1
147
+ lines_to_save.push(line)
148
+ end
149
+ }
150
+ }
151
+ File.truncate(context_file_path, 0)
152
+ File.open(context_file_path, "a") { |file|
153
+ lines_to_save.each { |line|
154
+ file.write(line)
155
+ }
156
+ }
157
+ log("Deleting file")
158
+ return
159
+ elsif input.to_i <= 0 || input.to_i > counter
160
+ log("Please enter a number between 1 and #{counter - 1}")
161
+ log("Enter 'a' to abort.")
162
+ end
163
+ end
164
+ end
86
165
 
87
166
  end
@@ -0,0 +1,7 @@
1
+ class FileFormatError < StandardError
2
+
3
+ def initialize(msg="File format not supported")
4
+ super
5
+ end
6
+
7
+ end
data/lib/files.rb CHANGED
@@ -1,21 +1,21 @@
1
1
  module Files
2
- def self.root
2
+ def root
3
3
  File.expand_path("./../", __dir__)
4
4
  end
5
5
 
6
- def self.file_path
6
+ def file_path
7
7
  File.expand_path("./../files/", __dir__)
8
8
  end
9
9
 
10
- def self.context_path
10
+ def context_path
11
11
  File.expand_path("./../files/context.jsonl", __dir__)
12
12
  end
13
13
 
14
- def self.config_path
14
+ def config_path
15
15
  File.expand_path("./../config/config.yml", __dir__)
16
16
  end
17
17
 
18
- def self.context_file_path
18
+ def context_file_path
19
19
  File.expand_path("./../files/context_file.txt", __dir__)
20
20
  end
21
21
  end
data/lib/help.rb CHANGED
@@ -1,85 +1,50 @@
1
1
  require 'rubygems'
2
2
 
3
- class Help
4
- def self.display_help()
5
- Logging.log("Usage: aa [options] [input]")
6
- Logging.log(" -lf, --loadfile <path>: Load file into context")
7
- Logging.log(" -f, --file: Read from context file")
8
- Logging.log(" -c, --conversation: Append to conversation (max 10 Questions / Answers pairs saved)")
9
- Logging.log(" -d, --delete: Delete conversation")
10
- Logging.log(" -i, --interactive: Interactive mode, always a conversation. Clear context with 'clear' (exit with 'exit' or 'quit')")
11
- Logging.log(" -w, --whisper <path>: Transcribe audio file")
12
- Logging.log(" -t, --translate <path>: Translate audio file")
13
- Logging.log("\n Options:")
14
- Logging.log(" --config: Edit config file")
15
- Logging.log(" -v, --version: Display version")
16
- Logging.log(" -h, --help: Display this help message")
17
-
18
- end
19
-
20
- def self.display_api_key()
21
- Logging.log("You need to set your API key in the file: ./config/config.yml")
22
- Logging.log("Create the file if it doesn't exist.")
23
- Logging.log("Add the following line to the file:")
24
- Logging.log(" OPENAI_API_KEY: <your API key>")
25
- Logging.log("You can get your API key from: https://openai.com/")
26
- end
27
-
28
- ## This don't work yet. Need to rework the way we handle args.
29
- def self.display_help_file()
30
- ## TODO: How to work with files
31
- end
32
3
 
33
- ## This don't work yet. Need to rework the way we handle args.
34
- def self.display_help_conversation()
35
- ## TODO: How to work with conversation
36
- end
4
+ class Help
5
+ extend Logging
37
6
 
38
7
  def self.display_version()
39
- spec = Gem::Specification::load("ask-ai.gemspec")
40
- Logging.log("Version: #{spec.version}")
41
- end
42
-
43
- def self.display_usage()
44
- Logging.log("Usage: ./main.rb [options] [input]")
45
-
46
- Logging.log("There are two types of options, flags and arguments.")
8
+ ## Without using gemspec
9
+ spec = Gem::Specification::find_by_name("ask-ai")
10
+ log("Version: ask-ai-#{spec.version}")
47
11
  end
48
12
 
49
13
  def self.interactive_help(command)
50
14
  case command
51
15
  when '-w'
52
- Logging.log("Ex: -w /home/name/sound_file.m4a")
53
- Logging.log("Will transcribe the audio file.")
16
+ log("Ex: -w /home/name/sound_file.m4a")
17
+ log("Will transcribe the audio file.")
54
18
  when '-t'
55
- Logging.log("Ex: -t /home/name/sound_file.m4a")
56
- Logging.log("Will translate the audio file to English.")
19
+ log("Ex: -t /home/name/sound_file.m4a")
20
+ log("Will translate the audio file to English.")
57
21
  when '-lf'
58
- Logging.log("Ex: -lf /home/name/some_text_file.txt'")
59
- Logging.log("Will load the file into context.")
60
- Logging.log("The file should a [txt, CSV]. More formats coming soon.")
22
+ log("Ex: -lf /home/name/some_text_file.txt'")
23
+ log("Will load the file into context.")
24
+ log("The file should a [txt, CSV]. More formats coming soon.")
61
25
  when '-f'
62
- Logging.log("Ex: -f Can you describe the file i provided?")
26
+ log("Ex: -f Can you describe the file i provided?")
63
27
  when 'config'
64
- Logging.log("Ex: config key <your API key>")
65
- Logging.log("Ex: config temp <0.0 - 1.0>")
66
- Logging.log("Ex: config context <0 - 100>")
67
- Logging.log("Beaware that the more context you use, the more expensive it will be.")
28
+ log("Ex: config key <your API key>")
29
+ log("Ex: config temp <0.0 - 1.0>")
30
+ log("Ex: config context <0 - 100>")
31
+ log("Beaware that the more context you use, the more expensive it will be.")
68
32
  else
69
- Logging.log("No help for: #{command}")
33
+ log("No help for: #{command}")
70
34
  end
71
35
 
72
36
  end
73
37
 
74
38
  def self.interactive_desc()
75
- Logging.log("Type 'exit' or 'quit' to exit.")
76
- Logging.log("Type 'clear' to clear context.")
77
- Logging.log("Type 'show' to show context.")
78
- Logging.log("Type 'help' to show help.")
79
- Logging.log("Type 'config [key, temp, context]' to change config.")
80
- Logging.log("Type '-w <filepath>' to whisper transcribe.")
81
- Logging.log("Type '-t' <filepath> to whisper translate.")
82
- Logging.log("Type '-lf' <filepath> to load file.")
83
- Logging.log("Type '-f' to use loaded file as context.")
39
+ log("Type 'exit' or 'quit' to exit.")
40
+ log("Type 'clear' to clear context.")
41
+ log("Type 'show' to show context.")
42
+ log("Type 'help' to show help.")
43
+ log("Type 'config [key, temp, context]' to change config.")
44
+ log("Type '-w <filepath>' to whisper transcribe.")
45
+ log("Type '-t' <filepath> to whisper translate.")
46
+ log("Type '-lf' <filepath> to load file.")
47
+ log "Type '-df' to delete file context."
48
+ log("Type '-f' to use loaded file as context.")
84
49
  end
85
50
  end
data/lib/logging.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'logger'
2
2
 
3
3
  module Logging
4
- def self.log(msg)
4
+ def log(msg)
5
5
  logger = Logger.new(STDOUT)
6
6
  logger.formatter = proc { |_severity, _datetime, _progname, msg| "#{msg}\n" }
7
7
  logger.info(msg)
data/lib/main.rb CHANGED
@@ -6,192 +6,98 @@ require 'readline'
6
6
  require 'io/console'
7
7
  require_relative './logging.rb'
8
8
  require_relative './prompt.rb'
9
- require_relative './handle_args.rb'
10
9
  require_relative './help.rb'
11
10
  require_relative './context.rb'
12
11
  require_relative './files.rb'
13
12
  require_relative './config.rb'
14
13
 
15
14
  class Main
16
- include Logging, Files, Config
15
+ extend Logging, Files, Config
16
+ LIST = [
17
+ "exit", "quit", "version", "clear", "help", "show",
18
+ "-w", "-t", "-lf", "-f", "-df", "config", "temp", "context"
19
+ ].sort
17
20
 
18
21
  def self.run()
19
- config = load_env()
20
22
 
21
- ## This does not work. Need to fix this.
22
- if (config == false || config.nil?)
23
- Logging.log("No API key found.")
24
- Help.display_api_key()
25
- Logging.log('If you want to set your API key now, type (y)es and press enter.')
26
- while input = Readline.readline("> ", true) do
27
- if input.empty?
28
- Logging.log("Exiting.")
29
- exit
30
- elsif input == "y" || input == "yes" || input == "Y" || input == "Yes"
31
- set_key()
32
- break
33
- else
34
- Logging.log('Invalid input (y)es or press enter to exit.')
35
- end
36
- end
37
- end
23
+ ## When using Readline, TAB will auto-complete
24
+ ## But it will only auto-complete from the LIST
25
+ ## Need to handle directories and files when -lf, -w, -t are used
26
+ #comp = proc { |s| LIST.grep(/^#{Regexp.escape(s)}/) }
27
+ #Readline.completion_append_character = ""
28
+ #Readline.completion_proc = comp
38
29
 
39
- context = Context.load_context()
40
- options_and_input = HandleArgs.handle_args()
41
- options = options_and_input.select { |k, v| k.start_with?("option_") }
42
- input = options_and_input["input"]
30
+ ## Function that when called activates completion from LIST
31
+ # def self.list_auto_complete()
32
+ # Readline.completion_append_character = ""
33
+ # Readline.completion_proc = proc do |s|
34
+ # if s.start_with?("-w", "-t", "-lf", "-f")
35
+ # pattern = s.sub(/^-w/, "") + "*"
36
+ # Dir.glob(pattern).grep(/^#{Regexp.escape('')}/)
37
+ # else
38
+ # LIST.grep(/^#{Regexp.escape(s)}/)
39
+ # end
40
+ # end
41
+ # end
43
42
 
44
- halt_options = ["-h", "--help", "-v", "--version", "--key"]
45
-
46
- ## Hack... Need to fix this.
47
- if options.empty?
48
- options = { "option_0" => "simple" }
49
- end
43
+ # list_auto_complete()
50
44
 
51
- options.each do |k, v|
52
- if halt_options.include?(v)
53
- ## Options that halt the program.
54
- case v
55
- when "-h", "--help"
56
- Help.display_help()
57
- exit
58
- when "-v", "--version"
59
- Help.display_version()
60
- exit
61
-
62
- when "--key"
63
- set_key(api_key: nil)
64
- else
65
- Help.display_help()
66
- exit
45
+ Help.interactive_desc()
46
+ while input = Readline.readline("\n> ", true) do
47
+ case input
48
+ when "exit", "quit"
49
+ break
50
+ when "version"
51
+ Help.display_version()
52
+ when "clear"
53
+ log("Clearing context...")
54
+ Context.delete_context()
55
+ when 'help'
56
+ Help.interactive_desc()
57
+ when /^help/
58
+ strip_input = input.sub(/^help/, "").strip
59
+ Help.interactive_help(strip_input)
60
+ when "show"
61
+ log("\n")
62
+ log(Context.load_context())
63
+ when /^-w/
64
+ stript_input = input.sub(/^-w/, "").strip
65
+ log(Prompt.whisper_transcribe(stript_input, interactive: true))
66
+ when /^-t/
67
+ stript_input = input.sub(/^-t/, "").strip
68
+ log(Prompt.whisper_translate(stript_input, interactive: true))
69
+ when /^-lf/
70
+ stript_input = input.sub(/^-lf/, "").strip
71
+ log("Loading File #{stript_input}")
72
+ Context.save_context_file(stript_input)
73
+ when /^-df/
74
+ Context.delete_file_context()
75
+ when /^-f/
76
+ stript_input = input.sub(/^-f/, "").strip
77
+ file_as_string = Context.load_context_file()
78
+ if file_as_string.empty?
79
+ log("No file loaded.")
80
+ next
67
81
  end
68
- else
69
- ## Options that don't halt the program.
70
- case v
71
- when "-f", "--file"
72
- file_as_string = Context.load_context_file()
73
- if file_as_string.empty?
74
- exit
75
- end
76
- Prompt.stream_prompt(input, file_as_string)
77
- when "-lf", "--loadfile"
78
- Logging.log("Loading File #{input}")
79
- Context.save_context_file(input)
80
- when "-d", "--delete"
81
- if input.nil?
82
- Context.delete_context()
83
- else
84
- Context.delete_context()
85
- Context.save_context(Prompt.stream_prompt(input, context))
86
- end
87
- when "-c", "--conversation"
88
- Logging.log(input)
89
- Context.save_context(Prompt.stream_prompt(input, context))
90
- when "-w", "--whisper"
91
- Logging.log(Prompt.whisper_transcribe(input))
92
- when "-t", "--translate"
93
- Logging.log(Prompt.whisper_translate(input))
94
- when "-i", "--interactive"
95
- Logging.log("Interactive mode...")
96
- Help.interactive_desc()
97
- while input = Readline.readline("\n> ", true) do
98
- case input
99
- when "exit", "quit"
100
- break
101
- when "clear"
102
- Logging.log("Clearing context...")
103
- Context.delete_context()
104
- when 'help'
105
- Help.interactive_desc()
106
- when /^help/
107
- strip_input = input.sub(/^help/, "").strip
108
- #TODO: This should be a specific help for interactive.
109
- Help.interactive_help(strip_input)
110
- when "show"
111
- Logging.log("\n")
112
- Logging.log(Context.load_context())
113
- when /^-w/
114
- stript_input = input.sub(/^-w/, "").strip
115
- Logging.log(Prompt.whisper_transcribe(stript_input, interactive: true))
116
- when /^-t/
117
- stript_input = input.sub(/^-t/, "").strip
118
- Logging.log(Prompt.whisper_translate(stript_input, interactive: true))
119
- when /^-lf/
120
- stript_input = input.sub(/^-lf/, "").strip
121
- Logging.log("Loading File #{stript_input}")
122
- Context.save_context_file(stript_input)
123
- when /^-f/
124
- stript_input = input.sub(/^-f/, "").strip
125
- file_as_string = Context.load_context_file()
126
- if file_as_string.empty?
127
- Logging.log("No file loaded.")
128
- next
129
- end
130
- Logging.log("")
131
- Prompt.stream_prompt(stript_input, file_as_string)
132
- Logging.log("")
133
- when ""
134
- Logging.log("No input given.")
135
- when /^config/
136
- strip_input = input.sub(/^config/, "").strip
137
- Config.set_config(strip_input)
138
- #set_key(api_key: nil)
139
- else
140
- #options_and_input = HandleArgs.handle_args()
141
- context = Context.load_context()
142
- Logging.log("")
143
- Context.save_context(Prompt.stream_prompt(input, context))
144
- Logging.log("")
145
- end
146
- end
147
- Logging.log("Exiting...")
148
- when "simple"
149
- if !input.nil?
150
- Prompt.stream_prompt(input)
151
- else
152
- Logging.log("No input given.")
153
- Help.display_help()
154
- end
155
- else
156
- Help.display_help()
157
- end
158
- end
159
- end
160
- end
161
-
162
- private
163
-
164
- def self.set_key(api_key: nil)
165
- if api_key.nil?
166
- Logging.log("Setting API key...")
167
- Logging.log("Enter API key: (or press enter to exit)")
168
-
169
- while input = Readline.readline("> ", true) do
170
- if input.empty?
171
- Logging.log("Exiting.")
172
- exit
173
- else
174
- api_key = input.strip
175
- break
82
+ context = Context.load_context(file_with_context: true)
83
+ log("")
84
+ Context.save_context(Prompt.stream_prompt(stript_input, context))
85
+ log("")
86
+ when ""
87
+ log("No input given.")
88
+ when /^config/
89
+ strip_input = input.sub(/^config/, "").strip
90
+ res = set_config(strip_input)
91
+ if res.is_a?(String)
92
+ log(res)
176
93
  end
94
+ else
95
+ context = Context.load_context()
96
+ log("")
97
+ Context.save_context(Prompt.stream_prompt(input, context))
98
+ log("")
177
99
  end
178
- Logging.log("Saving API key...")
179
100
  end
180
-
181
- FileUtils.mkdir_p(File.dirname(Files.config_path))
182
- File.open(Files.config_path, "w") do |f|
183
- f.write(YAML.dump({ "OPENAI_API_KEY" => api_key }))
184
- end
185
- Logging.log("API key saved.")
186
- Logging.log("")
187
- end
188
-
189
- def self.load_env()
190
- #Config.load_key()
191
- YAML.load(File.read(Files.config_path))
192
-
193
- rescue Errno::ENOENT
194
- Logging.log("No config.yml found.")
195
101
  end
196
102
  end
197
103
 
data/lib/prompt.rb CHANGED
@@ -3,10 +3,9 @@ require_relative './files.rb'
3
3
  require_relative './config.rb'
4
4
 
5
5
  class Prompt
6
- include Files
7
- include Config
6
+ extend Files, Config, Logging
8
7
  ## Streams the response, VERY NICE
9
- def self.stream_prompt(input, conversation = '', temp = Config.load_temperature())
8
+ def self.stream_prompt(input, conversation = '', temp = load_temperature())
10
9
  if temp.nil?
11
10
  temp = 0.7
12
11
  end
@@ -16,23 +15,25 @@ class Prompt
16
15
  conversation += "\n My question: #{input}"
17
16
  end
18
17
  response = ''
19
- client.chat(
20
- parameters: {
21
- model: "gpt-3.5-turbo",
22
- messages: [{ role: "user", content: conversation}],
23
- temperature: temp, ## Should be a parameter
24
- stream: proc do |chunk, _bytesize|
25
- response += chunk.dig("choices", 0, "delta", "content") unless chunk.dig("choices", 0, "delta", "content").nil?
26
- print chunk.dig("choices", 0, "delta", "content")
27
- end
18
+ unless client.nil?
19
+ client.chat(
20
+ parameters: {
21
+ model: "gpt-3.5-turbo",
22
+ messages: [{ role: "user", content: conversation}],
23
+ temperature: temp,
24
+ stream: proc do |chunk, _bytesize|
25
+ response += chunk.dig("choices", 0, "delta", "content") unless chunk.dig("choices", 0, "delta", "content").nil?
26
+ print chunk.dig("choices", 0, "delta", "content")
27
+ end
28
+ }
29
+ )
30
+ context = {
31
+ "input" => input,
32
+ "response" => response,
28
33
  }
29
- )
30
- context = {
31
- "input" => input,
32
- "response" => response,
33
- }
34
34
 
35
- return context
35
+ return context
36
+ end
36
37
  end
37
38
 
38
39
  ## Not implemented only scaffolding
@@ -47,7 +48,7 @@ class Prompt
47
48
 
48
49
  def self.whisper_translate(file_path, interactive = false)
49
50
  if (file_path.nil? || !file_path.end_with?(*['.mp3', '.wav', '.m4a', '.webm', '.mpeg', '.mpga']))
50
- Logging.log("No file given or wrong file type")
51
+ log("No file given or wrong file type")
51
52
  unless interactive
52
53
  exit
53
54
  end
@@ -59,27 +60,29 @@ class Prompt
59
60
  exit
60
61
  end
61
62
  else
62
- response = client.audio.translate(
63
- parameters: {
64
- model: "whisper-1",
65
- file: File.open(file_path, "rb"),
66
- })
67
- if (response["text"].nil? || response["text"].empty?)
68
- Logging.log("No text found")
69
- unless interactive
70
- exit
63
+ unless client.nil?
64
+ response = client.audio.translate(
65
+ parameters: {
66
+ model: "whisper-1",
67
+ file: File.open(file_path, "rb"),
68
+ })
69
+ if (response["text"].nil? || response["text"].empty?)
70
+ log("No text found")
71
+ unless interactive
72
+ exit
73
+ end
71
74
  end
75
+ return response["text"]
72
76
  end
73
- return response["text"]
74
77
  end
75
78
  end
76
79
  rescue Errno::ENOENT => e
77
- Logging.log(e)
80
+ log(e)
78
81
  end
79
82
 
80
83
  def self.whisper_transcribe(file_path, interactive = false)
81
84
  if (file_path.nil? || !file_path.end_with?(*['.mp3', '.wav', '.m4a', '.webm', '.mpeg', '.mpga']))
82
- Logging.log("No file given")
85
+ log("No file given")
83
86
  unless interactive
84
87
  exit
85
88
  end
@@ -91,31 +94,40 @@ class Prompt
91
94
  exit
92
95
  end
93
96
  else
94
- response = client.audio.transcribe(
95
- parameters: {
96
- model: "whisper-1",
97
- file: File.open(file_path, "rb"),
98
- })
99
- if (response["text"].nil? || response["text"].empty?)
100
- Logging.log("No text found")
101
- unless interactive
102
- exit
97
+ unless client.nil?
98
+ response = client.audio.transcribe(
99
+ parameters: {
100
+ model: "whisper-1",
101
+ file: File.open(file_path, "rb"),
102
+ })
103
+ if (response["text"].nil? || response["text"].empty?)
104
+ log("No text found")
105
+ unless interactive
106
+ exit
107
+ end
103
108
  end
109
+ return response["text"]
104
110
  end
105
- return response["text"]
106
111
  end
107
112
  end
108
113
  rescue Errno::ENOENT => e
109
114
  #Logging.log("File not found")
110
- Logging.log(e)
115
+ log(e)
111
116
  end
112
117
 
113
118
  private
114
119
 
115
120
  def self.client()
116
- conf = YAML.load(File.read(Files.config_path))
117
- key = conf["OPENAI_API_KEY"]
121
+ conf = YAML.load(File.read(config_path))
122
+ unless (conf == false || conf.nil?)
123
+ key = conf["OPENAI_API_KEY"]
124
+ end
118
125
 
119
- OpenAI::Client.new(access_token: key)
126
+ begin
127
+ OpenAI::Client.new(access_token: key)
128
+ rescue OpenAI::ConfigurationError => e
129
+ log("OpenAI API key not found, run 'config key' to set it")
130
+ return nil
131
+ end
120
132
  end
121
133
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oskar Franck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-04 00:00:00.000000000 Z
11
+ date: 2023-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-openai
@@ -38,8 +38,8 @@ files:
38
38
  - files/context_file.txt
39
39
  - lib/config.rb
40
40
  - lib/context.rb
41
+ - lib/file_format_error.rb
41
42
  - lib/files.rb
42
- - lib/handle_args.rb
43
43
  - lib/help.rb
44
44
  - lib/logging.rb
45
45
  - lib/main.rb
data/lib/handle_args.rb DELETED
@@ -1,61 +0,0 @@
1
- class HandleArgs
2
- def self.permitted_options()
3
- ## TODO: Add more options.
4
- ## -t --translate
5
- ## -temp --temperature (need to handle value after input)
6
-
7
- [
8
- '-h',
9
- '--help',
10
- '-v',
11
- '--version',
12
- '-f',
13
- '--file',
14
- '-lf',
15
- '--loadfile',
16
- '-d',
17
- '--delete',
18
- '-c',
19
- '--conversation',
20
- '--finetune',
21
- '-w', '--whisper',
22
- '-t', '--translate',
23
- '-i', '--interactive',
24
- '--key',
25
- ]
26
- end
27
-
28
- def self.handle_args()
29
- p_args = HandleArgs.permitted_options()
30
- ## This is the hash that will be returned.
31
- args_hash = {}
32
-
33
- ## This handles args then called as ruby script.
34
- ARGV.each_with_index do |arg, i|
35
- #Logging.log("Arg: #{arg}")
36
-
37
- if arg.start_with?('-')
38
- ## This is an option.
39
- if p_args.include?(arg)
40
- ## This is a permitted / available option.
41
- #Logging.log("Option: #{arg}")
42
- args_hash["option_#{i}"] = arg
43
- else
44
- ## This is an unknown option.
45
- ## TODO: Handle unknown option. display help? discard?
46
- Logging.log("Unknown option: #{arg}")
47
- args_hash["option_#{i}"] = arg
48
- end
49
- else
50
- ## This is input.
51
- ## This if statement append all 'input' args to one string.
52
- if args_hash["input"].nil?
53
- args_hash["input"] = arg
54
- else
55
- args_hash["input"] = "#{args_hash['input']} #{arg}"
56
- end
57
- end
58
- end
59
- return args_hash
60
- end
61
- end