ask-ai 0.0.5 → 1.0.1
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/config/config.yml +0 -3
- data/lib/config.rb +76 -31
- data/lib/context.rb +101 -22
- data/lib/file_format_error.rb +7 -0
- data/lib/files.rb +5 -5
- data/lib/help.rb +28 -63
- data/lib/logging.rb +1 -1
- data/lib/main.rb +76 -170
- data/lib/prompt.rb +57 -45
- metadata +3 -3
- data/lib/handle_args.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e73628fad8025e8de63e07c4256f42426eb9651fb62a418f8f73c6b7b4db432d
|
4
|
+
data.tar.gz: 4575f5535ddedd005e846cf598c3f464717130b2ce065b7c29e2e174cadebd30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2fccb29fba0174e10556377b2422fb6d3de99eef58820af2289ef77dd0379029c5d989d745eb388ef0901c153de2ce22163a9fb8b11d1f133d84e0d3bdb6b62
|
7
|
+
data.tar.gz: 45bc0ea0ef5e78a465b196fe262a2b1b88cdeb2bd9b15af86300d72ac2cf220c4b1ebeb860b41782e02bad51dc87d34e32a7522c1ff3129bc1902995c0dfcebf
|
data/config/config.yml
CHANGED
data/lib/config.rb
CHANGED
@@ -1,65 +1,110 @@
|
|
1
1
|
require_relative './files.rb'
|
2
2
|
|
3
3
|
module Config
|
4
|
-
|
5
|
-
def
|
6
|
-
config = YAML.load_file(
|
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
|
11
|
-
config = YAML.load_file(
|
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(
|
16
|
+
File.open(config_path, 'w') { |f| YAML.dump(config, f) }
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
config = YAML.load_file(
|
18
|
-
config
|
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
|
22
|
-
config = YAML.load_file(
|
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(
|
32
|
+
File.open(config_path, 'w') { |f| YAML.dump(config, f) }
|
25
33
|
end
|
26
34
|
|
27
|
-
def
|
28
|
-
config = YAML.load_file(
|
29
|
-
config
|
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
|
33
|
-
config = YAML.load_file(
|
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(
|
48
|
+
File.open(config_path, 'w') { |f| YAML.dump(config, f) }
|
36
49
|
end
|
37
50
|
|
38
|
-
def
|
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
|
-
|
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.
|
50
|
-
|
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.
|
57
|
-
|
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
|
-
|
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
|
-
|
5
|
-
def self.load_context()
|
6
|
-
if File.exist?(
|
7
|
-
conversation = File.readlines(
|
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?(
|
32
|
-
File.open(
|
37
|
+
unless File.exist?(context_path)
|
38
|
+
File.open(context_path, "w") {}
|
33
39
|
end
|
34
|
-
File.readlines(
|
35
|
-
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(
|
40
|
-
tmp_arr.each { |line| File.open(
|
41
|
-
File.open(
|
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
|
-
|
46
|
-
File.truncate(
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
79
|
+
log("No file path given.")
|
68
80
|
end
|
69
81
|
rescue Errno::ENOENT
|
70
|
-
|
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(
|
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
|
-
|
83
|
-
|
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
|
data/lib/files.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
module Files
|
2
|
-
def
|
2
|
+
def root
|
3
3
|
File.expand_path("./../", __dir__)
|
4
4
|
end
|
5
5
|
|
6
|
-
def
|
6
|
+
def file_path
|
7
7
|
File.expand_path("./../files/", __dir__)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def context_path
|
11
11
|
File.expand_path("./../files/context.jsonl", __dir__)
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def config_path
|
15
15
|
File.expand_path("./../config/config.yml", __dir__)
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
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
|
-
|
34
|
-
|
35
|
-
## TODO: How to work with conversation
|
36
|
-
end
|
4
|
+
class Help
|
5
|
+
extend Logging
|
37
6
|
|
38
7
|
def self.display_version()
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
53
|
-
|
16
|
+
log("Ex: -w /home/name/sound_file.m4a")
|
17
|
+
log("Will transcribe the audio file.")
|
54
18
|
when '-t'
|
55
|
-
|
56
|
-
|
19
|
+
log("Ex: -t /home/name/sound_file.m4a")
|
20
|
+
log("Will translate the audio file to English.")
|
57
21
|
when '-lf'
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
26
|
+
log("Ex: -f Can you describe the file i provided?")
|
63
27
|
when 'config'
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
33
|
+
log("No help for: #{command}")
|
70
34
|
end
|
71
35
|
|
72
36
|
end
|
73
37
|
|
74
38
|
def self.interactive_desc()
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
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
|
-
|
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
|
-
##
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
7
|
-
include Config
|
6
|
+
extend Files, Config, Logging
|
8
7
|
## Streams the response, VERY NICE
|
9
|
-
def self.stream_prompt(input, conversation = '', temp =
|
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.
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
115
|
+
log(e)
|
111
116
|
end
|
112
117
|
|
113
118
|
private
|
114
119
|
|
115
120
|
def self.client()
|
116
|
-
conf = YAML.load(File.read(
|
117
|
-
|
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
|
-
|
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:
|
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-
|
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
|