ask-ai 0.0.4 → 1.0.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/lib/config.rb +110 -0
- data/lib/context.rb +25 -20
- data/lib/files.rb +5 -5
- data/lib/help.rb +38 -37
- data/lib/logging.rb +1 -1
- data/lib/main.rb +62 -157
- data/lib/prompt.rb +61 -44
- metadata +3 -4
- data/lib/handle_args.rb +0 -61
- data/lib/paths.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d0033fda00a5a8399a6e9b71c75338b02269df54edb791bd82215e939492d15
|
|
4
|
+
data.tar.gz: 391d4cc9fe240289358f2489b61224b8d0706254174f9ad4a5ec7a109b7b3a89
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f749624e465e35a8ebcae8b7b9b312d1f4ffdbd8e390043a0fc87dae5eaf680931975eb87369eba855043eff52206c2daf47af95fc5136e7fb0134520275a28
|
|
7
|
+
data.tar.gz: 81d6f56f465808352087d5f53bab44b7a1af2d63292dbba4b5fab7f91b44fd3d78c6a591718d142f6348c6ab78bae8a54d84b2cba73e2164efd32dd3b27d4e1d
|
data/lib/config.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require_relative './files.rb'
|
|
2
|
+
|
|
3
|
+
module Config
|
|
4
|
+
extend Files
|
|
5
|
+
def load_key()
|
|
6
|
+
config = YAML.load_file(config_path)
|
|
7
|
+
config['OPENAI_API_KEY']
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def save_key(api_key)
|
|
11
|
+
config = YAML.load_file(config_path)
|
|
12
|
+
if config == false
|
|
13
|
+
config = {}
|
|
14
|
+
end
|
|
15
|
+
config['OPENAI_API_KEY'] = api_key
|
|
16
|
+
File.open(config_path, 'w') { |f| YAML.dump(config, f) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load_temperature
|
|
20
|
+
config = YAML.load_file(config_path)
|
|
21
|
+
unless config == false
|
|
22
|
+
config['TEMPERATURE']
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def save_temperature(temperature)
|
|
27
|
+
config = YAML.load_file(config_path)
|
|
28
|
+
if config == false
|
|
29
|
+
config = {}
|
|
30
|
+
end
|
|
31
|
+
config['TEMPERATURE'] = temperature.to_f
|
|
32
|
+
File.open(config_path, 'w') { |f| YAML.dump(config, f) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def load_context_length
|
|
36
|
+
config = YAML.load_file(config_path)
|
|
37
|
+
unless config == false
|
|
38
|
+
config['CONTEXT_LENGTH']
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def save_context_length(context_length)
|
|
43
|
+
config = YAML.load_file(config_path)
|
|
44
|
+
if config == false
|
|
45
|
+
config = {}
|
|
46
|
+
end
|
|
47
|
+
config['CONTEXT_LENGTH'] = context_length.to_i
|
|
48
|
+
File.open(config_path, 'w') { |f| YAML.dump(config, f) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def set_config(value)
|
|
52
|
+
if value.include?('key')
|
|
53
|
+
stript_value = value.sub(/^key/, '').strip
|
|
54
|
+
if stript_value.empty?
|
|
55
|
+
return 'No API key given'
|
|
56
|
+
end
|
|
57
|
+
save_key(stript_value)
|
|
58
|
+
return 'API key saved'
|
|
59
|
+
elsif value.include?('temp')
|
|
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'
|
|
63
|
+
end
|
|
64
|
+
save_temperature(stript_value)
|
|
65
|
+
return 'Temperature saved'
|
|
66
|
+
elsif value.include?('context')
|
|
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'
|
|
70
|
+
end
|
|
71
|
+
save_context_length(stript_value)
|
|
72
|
+
return 'Context length saved'
|
|
73
|
+
else
|
|
74
|
+
return 'Invalid config value'
|
|
75
|
+
end
|
|
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
|
|
110
|
+
end
|
data/lib/context.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
require_relative './files.rb'
|
|
2
|
-
|
|
2
|
+
require_relative './config.rb'
|
|
3
3
|
class Context
|
|
4
|
-
|
|
4
|
+
extend Files, Config, Logging
|
|
5
5
|
def self.load_context()
|
|
6
|
-
if File.exist?(
|
|
7
|
-
conversation = File.readlines(
|
|
6
|
+
if File.exist?(context_path)
|
|
7
|
+
conversation = File.readlines(context_path).map { |line| JSON.parse(line) }
|
|
8
8
|
else
|
|
9
9
|
conversation = []
|
|
10
10
|
end
|
|
@@ -27,46 +27,51 @@ class Context
|
|
|
27
27
|
## Here we save the context to a file.
|
|
28
28
|
## Max 10 previous Q / A to save tokens.
|
|
29
29
|
def self.save_context(context)
|
|
30
|
+
return if context.nil?
|
|
30
31
|
tmp_arr = []
|
|
31
|
-
unless File.exist?(
|
|
32
|
-
File.open(
|
|
32
|
+
unless File.exist?(context_path)
|
|
33
|
+
File.open(context_path, "w") {}
|
|
33
34
|
end
|
|
34
|
-
File.readlines(
|
|
35
|
-
|
|
35
|
+
File.readlines(context_path).map { |line| tmp_arr.push(JSON.parse(line)) }
|
|
36
|
+
length = load_context_length().nil? ? 10 : load_context_length()
|
|
37
|
+
if tmp_arr.length > length
|
|
36
38
|
tmp_arr.shift()
|
|
37
39
|
end
|
|
38
|
-
File.truncate(
|
|
39
|
-
tmp_arr.each { |line| File.open(
|
|
40
|
-
File.open(
|
|
40
|
+
File.truncate(context_path, 0)
|
|
41
|
+
tmp_arr.each { |line| File.open(context_path, "a") { |file| file.write("#{line.to_json}\n") } }
|
|
42
|
+
File.open(context_path, "a") { |file| file.write("#{context.to_json}\n") }
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
def self.delete_context()
|
|
44
|
-
|
|
45
|
-
File.truncate(
|
|
46
|
+
log("Deleting previous context.")
|
|
47
|
+
File.truncate(context_path, 0)
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def self.save_context_file(file_path)
|
|
49
51
|
unless file_path.nil?
|
|
50
52
|
file_in = File.open(file_path, 'r')
|
|
51
|
-
file_out = File.open(
|
|
53
|
+
file_out = File.open(context_file_path, 'w')
|
|
52
54
|
char_count = 0
|
|
53
55
|
file_in.each do |line|
|
|
56
|
+
puts "Line: #{line}"
|
|
54
57
|
char_count += line.length
|
|
55
58
|
file_out.write(line)
|
|
56
59
|
end
|
|
60
|
+
file_in.close
|
|
61
|
+
file_out.close
|
|
57
62
|
|
|
58
63
|
if char_count > 10000
|
|
59
|
-
|
|
64
|
+
log("Warning: The file you are trying to feed to the API is #{char_count} characters long. This consumes a lot of tokens.")
|
|
60
65
|
end
|
|
61
66
|
else
|
|
62
|
-
|
|
67
|
+
log("No file path given.")
|
|
63
68
|
end
|
|
64
69
|
rescue Errno::ENOENT
|
|
65
|
-
|
|
70
|
+
log("No file at '#{file_path}' found.")
|
|
66
71
|
end
|
|
67
72
|
|
|
68
73
|
def self.load_context_file()
|
|
69
|
-
file = File.open(
|
|
74
|
+
file = File.open(context_file_path, 'r')
|
|
70
75
|
file_as_string = ""
|
|
71
76
|
file.each do |line|
|
|
72
77
|
file_as_string += line
|
|
@@ -74,8 +79,8 @@ class Context
|
|
|
74
79
|
|
|
75
80
|
return file_as_string
|
|
76
81
|
rescue Errno::ENOENT
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
log("No file at '#{context_file_path}' found.")
|
|
83
|
+
log("Load a file with 'aa -lf <file_path>'")
|
|
79
84
|
return ""
|
|
80
85
|
end
|
|
81
86
|
|
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,48 +1,49 @@
|
|
|
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
3
|
|
|
18
|
-
|
|
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
|
|
4
|
+
class Help
|
|
5
|
+
extend Logging
|
|
27
6
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
7
|
+
def self.display_version()
|
|
8
|
+
## Without using gemspec
|
|
9
|
+
spec = Gem::Specification::find_by_name("ask-ai")
|
|
10
|
+
log("Version: ask-ai-#{spec.version}")
|
|
31
11
|
end
|
|
32
12
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
13
|
+
def self.interactive_help(command)
|
|
14
|
+
case command
|
|
15
|
+
when '-w'
|
|
16
|
+
log("Ex: -w /home/name/sound_file.m4a")
|
|
17
|
+
log("Will transcribe the audio file.")
|
|
18
|
+
when '-t'
|
|
19
|
+
log("Ex: -t /home/name/sound_file.m4a")
|
|
20
|
+
log("Will translate the audio file to English.")
|
|
21
|
+
when '-lf'
|
|
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.")
|
|
25
|
+
when '-f'
|
|
26
|
+
log("Ex: -f Can you describe the file i provided?")
|
|
27
|
+
when 'config'
|
|
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.")
|
|
32
|
+
else
|
|
33
|
+
log("No help for: #{command}")
|
|
34
|
+
end
|
|
37
35
|
|
|
38
|
-
def self.display_version()
|
|
39
|
-
spec = Gem::Specification::load("ask-ai.gemspec")
|
|
40
|
-
Logging.log("Version: #{spec.version}")
|
|
41
36
|
end
|
|
42
37
|
|
|
43
|
-
def self.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
def self.interactive_desc()
|
|
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 '-f' to use loaded file as context.")
|
|
47
48
|
end
|
|
48
49
|
end
|
data/lib/logging.rb
CHANGED
data/lib/main.rb
CHANGED
|
@@ -6,175 +6,80 @@ 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'
|
|
12
|
+
require_relative './config.rb'
|
|
13
13
|
|
|
14
14
|
class Main
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
config
|
|
19
|
-
|
|
20
|
-
## This does not work. Need to fix this.
|
|
21
|
-
if config == false
|
|
22
|
-
Logging.log("No API key found.")
|
|
23
|
-
Help.display_api_key()
|
|
24
|
-
Logging.log('If you want to set your API key now, type (y)es and press enter.')
|
|
25
|
-
while input = Readline.readline("> ", true) do
|
|
26
|
-
if input.empty?
|
|
27
|
-
Logging.log("Exiting.")
|
|
28
|
-
exit
|
|
29
|
-
elsif input == "y" || input == "yes" || input == "Y" || input == "Yes"
|
|
30
|
-
set_key()
|
|
31
|
-
exit
|
|
32
|
-
else
|
|
33
|
-
Logging.log('Invalid input (y)es or press enter to exit.')
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
context = Context.load_context()
|
|
39
|
-
options_and_input = HandleArgs.handle_args()
|
|
40
|
-
options = options_and_input.select { |k, v| k.start_with?("option_") }
|
|
41
|
-
input = options_and_input["input"]
|
|
15
|
+
extend Logging, Files, Config
|
|
16
|
+
LIST = [
|
|
17
|
+
"exit", "quit", "version", "clear", "help", "show",
|
|
18
|
+
"-w", "-t", "-lf", "-f", "config", "temp", "context"
|
|
19
|
+
].sort
|
|
42
20
|
|
|
43
|
-
|
|
21
|
+
def self.run()
|
|
44
22
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
49
29
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
30
|
+
Help.interactive_desc()
|
|
31
|
+
while input = Readline.readline("\n> ", true) do
|
|
32
|
+
case input
|
|
33
|
+
when "exit", "quit"
|
|
34
|
+
break
|
|
35
|
+
when "version"
|
|
36
|
+
Help.display_version()
|
|
37
|
+
when "clear"
|
|
38
|
+
log("Clearing context...")
|
|
39
|
+
Context.delete_context()
|
|
40
|
+
when 'help'
|
|
41
|
+
Help.interactive_desc()
|
|
42
|
+
when /^help/
|
|
43
|
+
strip_input = input.sub(/^help/, "").strip
|
|
44
|
+
Help.interactive_help(strip_input)
|
|
45
|
+
when "show"
|
|
46
|
+
log("\n")
|
|
47
|
+
log(Context.load_context())
|
|
48
|
+
when /^-w/
|
|
49
|
+
stript_input = input.sub(/^-w/, "").strip
|
|
50
|
+
log(Prompt.whisper_transcribe(stript_input, interactive: true))
|
|
51
|
+
when /^-t/
|
|
52
|
+
stript_input = input.sub(/^-t/, "").strip
|
|
53
|
+
log(Prompt.whisper_translate(stript_input, interactive: true))
|
|
54
|
+
when /^-lf/
|
|
55
|
+
stript_input = input.sub(/^-lf/, "").strip
|
|
56
|
+
log("Loading File #{stript_input}")
|
|
57
|
+
Context.save_context_file(stript_input)
|
|
58
|
+
when /^-f/
|
|
59
|
+
stript_input = input.sub(/^-f/, "").strip
|
|
60
|
+
file_as_string = Context.load_context_file()
|
|
61
|
+
if file_as_string.empty?
|
|
62
|
+
log("No file loaded.")
|
|
63
|
+
next
|
|
66
64
|
end
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Logging.log("Loading File #{input}")
|
|
78
|
-
Context.save_context_file(input)
|
|
79
|
-
when "-d", "--delete"
|
|
80
|
-
if input.nil?
|
|
81
|
-
Context.delete_context()
|
|
82
|
-
else
|
|
83
|
-
Context.delete_context()
|
|
84
|
-
Context.save_context(Prompt.stream_prompt(input, context))
|
|
85
|
-
end
|
|
86
|
-
when "-c", "--conversation"
|
|
87
|
-
Logging.log(input)
|
|
88
|
-
Context.save_context(Prompt.stream_prompt(input, context))
|
|
89
|
-
when "-w", "--whisper"
|
|
90
|
-
Logging.log(Prompt.whisper_transcribe(input))
|
|
91
|
-
when "-t", "--translate"
|
|
92
|
-
Logging.log(Prompt.whisper_translate(input))
|
|
93
|
-
when "-i", "--interactive"
|
|
94
|
-
Logging.log("Interactive mode...")
|
|
95
|
-
Logging.log("Type 'exit' or 'quit' to exit.")
|
|
96
|
-
Logging.log("Type 'clear' to clear context.")
|
|
97
|
-
Logging.log("Type 'show' to show context.")
|
|
98
|
-
Logging.log("Type 'help' to show help.")
|
|
99
|
-
Logging.log("Type 'config' to change config.")
|
|
100
|
-
Logging.log("Type '-w' to whisper transcribe.")
|
|
101
|
-
Logging.log("Type '-t' to whisper translate.")
|
|
102
|
-
|
|
103
|
-
while input = Readline.readline("\n> ", true) do
|
|
104
|
-
case input
|
|
105
|
-
when "exit", "quit"
|
|
106
|
-
break
|
|
107
|
-
when "clear"
|
|
108
|
-
Logging.log("Clearing context...")
|
|
109
|
-
Context.delete_context()
|
|
110
|
-
when "help"
|
|
111
|
-
#TODO: This should be a specific help for interactive.
|
|
112
|
-
Help.display_help()
|
|
113
|
-
when "show"
|
|
114
|
-
Logging.log("\n")
|
|
115
|
-
Logging.log(Context.load_context())
|
|
116
|
-
when /^-w/
|
|
117
|
-
stript_input = input.sub(/^-w/, "").strip
|
|
118
|
-
Logging.log(Prompt.whisper_transcribe(stript_input, interactive: true))
|
|
119
|
-
when /^-t/
|
|
120
|
-
stript_input = input.sub(/^-t/, "").strip
|
|
121
|
-
Logging.log(Prompt.whisper_translate(stript_input, interactive: true))
|
|
122
|
-
when "config"
|
|
123
|
-
set_key(api_key: nil)
|
|
124
|
-
else
|
|
125
|
-
#options_and_input = HandleArgs.handle_args()
|
|
126
|
-
context = Context.load_context()
|
|
127
|
-
Logging.log("")
|
|
128
|
-
Context.save_context(Prompt.stream_prompt(input, context))
|
|
129
|
-
Logging.log("")
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
Logging.log("Exiting...")
|
|
133
|
-
when "simple"
|
|
134
|
-
if !input.nil?
|
|
135
|
-
Prompt.stream_prompt(input)
|
|
136
|
-
else
|
|
137
|
-
Logging.log("No input given.")
|
|
138
|
-
Help.display_help()
|
|
139
|
-
end
|
|
140
|
-
else
|
|
141
|
-
Help.display_help()
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
private
|
|
148
|
-
|
|
149
|
-
def self.set_key(api_key: nil)
|
|
150
|
-
if api_key.nil?
|
|
151
|
-
Logging.log("Setting API key...")
|
|
152
|
-
Logging.log("Enter API key: (or press enter to exit)")
|
|
153
|
-
|
|
154
|
-
while input = Readline.readline("> ", true) do
|
|
155
|
-
if input.empty?
|
|
156
|
-
Logging.log("Exiting.")
|
|
157
|
-
exit
|
|
158
|
-
else
|
|
159
|
-
api_key = input.strip
|
|
160
|
-
break
|
|
65
|
+
log("")
|
|
66
|
+
Prompt.stream_prompt(stript_input, file_as_string)
|
|
67
|
+
log("")
|
|
68
|
+
when ""
|
|
69
|
+
log("No input given.")
|
|
70
|
+
when /^config/
|
|
71
|
+
strip_input = input.sub(/^config/, "").strip
|
|
72
|
+
res = set_config(strip_input)
|
|
73
|
+
if res.is_a?(String)
|
|
74
|
+
log(res)
|
|
161
75
|
end
|
|
76
|
+
else
|
|
77
|
+
context = Context.load_context()
|
|
78
|
+
log("")
|
|
79
|
+
Context.save_context(Prompt.stream_prompt(input, context))
|
|
80
|
+
log("")
|
|
162
81
|
end
|
|
163
|
-
Logging.log("Saving API key...")
|
|
164
82
|
end
|
|
165
|
-
|
|
166
|
-
FileUtils.mkdir_p(File.dirname(Files.config_path))
|
|
167
|
-
File.open(Files.config_path, "w") do |f|
|
|
168
|
-
f.write(YAML.dump({ "OPENAI_API_KEY" => api_key }))
|
|
169
|
-
end
|
|
170
|
-
Logging.log("API key saved.")
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def self.load_env()
|
|
174
|
-
YAML.load(File.read(Files.config_path))
|
|
175
|
-
|
|
176
|
-
rescue Errno::ENOENT
|
|
177
|
-
Logging.log("No config.yml found.")
|
|
178
83
|
end
|
|
179
84
|
end
|
|
180
85
|
|
data/lib/prompt.rb
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
require "openai"
|
|
2
2
|
require_relative './files.rb'
|
|
3
|
+
require_relative './config.rb'
|
|
3
4
|
|
|
4
5
|
class Prompt
|
|
5
|
-
|
|
6
|
+
extend Files, Config, Logging
|
|
6
7
|
## Streams the response, VERY NICE
|
|
7
|
-
def self.stream_prompt(input, conversation = '', temp =
|
|
8
|
+
def self.stream_prompt(input, conversation = '', temp = load_temperature())
|
|
9
|
+
if temp.nil?
|
|
10
|
+
temp = 0.7
|
|
11
|
+
end
|
|
8
12
|
if conversation.length == 0
|
|
9
13
|
conversation += input
|
|
10
14
|
else
|
|
11
15
|
conversation += "\n My question: #{input}"
|
|
12
16
|
end
|
|
13
17
|
response = ''
|
|
14
|
-
client.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
unless client.nil?
|
|
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
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
context = {
|
|
31
|
+
"input" => input,
|
|
32
|
+
"response" => response,
|
|
23
33
|
}
|
|
24
|
-
)
|
|
25
|
-
context = {
|
|
26
|
-
"input" => input,
|
|
27
|
-
"response" => response,
|
|
28
|
-
}
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
return context
|
|
36
|
+
end
|
|
31
37
|
end
|
|
32
38
|
|
|
33
39
|
## Not implemented only scaffolding
|
|
@@ -42,7 +48,7 @@ class Prompt
|
|
|
42
48
|
|
|
43
49
|
def self.whisper_translate(file_path, interactive = false)
|
|
44
50
|
if (file_path.nil? || !file_path.end_with?(*['.mp3', '.wav', '.m4a', '.webm', '.mpeg', '.mpga']))
|
|
45
|
-
|
|
51
|
+
log("No file given or wrong file type")
|
|
46
52
|
unless interactive
|
|
47
53
|
exit
|
|
48
54
|
end
|
|
@@ -54,27 +60,29 @@ class Prompt
|
|
|
54
60
|
exit
|
|
55
61
|
end
|
|
56
62
|
else
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
66
74
|
end
|
|
75
|
+
return response["text"]
|
|
67
76
|
end
|
|
68
|
-
return response["text"]
|
|
69
77
|
end
|
|
70
78
|
end
|
|
71
79
|
rescue Errno::ENOENT => e
|
|
72
|
-
|
|
80
|
+
log(e)
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
def self.whisper_transcribe(file_path, interactive = false)
|
|
76
84
|
if (file_path.nil? || !file_path.end_with?(*['.mp3', '.wav', '.m4a', '.webm', '.mpeg', '.mpga']))
|
|
77
|
-
|
|
85
|
+
log("No file given")
|
|
78
86
|
unless interactive
|
|
79
87
|
exit
|
|
80
88
|
end
|
|
@@ -86,31 +94,40 @@ class Prompt
|
|
|
86
94
|
exit
|
|
87
95
|
end
|
|
88
96
|
else
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
98
108
|
end
|
|
109
|
+
return response["text"]
|
|
99
110
|
end
|
|
100
|
-
return response["text"]
|
|
101
111
|
end
|
|
102
112
|
end
|
|
103
113
|
rescue Errno::ENOENT => e
|
|
104
114
|
#Logging.log("File not found")
|
|
105
|
-
|
|
115
|
+
log(e)
|
|
106
116
|
end
|
|
107
117
|
|
|
108
118
|
private
|
|
109
119
|
|
|
110
120
|
def self.client()
|
|
111
|
-
conf = YAML.load(File.read(
|
|
112
|
-
|
|
121
|
+
conf = YAML.load(File.read(config_path))
|
|
122
|
+
unless conf == false
|
|
123
|
+
key = conf["OPENAI_API_KEY"]
|
|
124
|
+
end
|
|
113
125
|
|
|
114
|
-
|
|
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
|
|
115
132
|
end
|
|
116
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
|
|
4
|
+
version: 1.0.0
|
|
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-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby-openai
|
|
@@ -36,13 +36,12 @@ files:
|
|
|
36
36
|
- config/config.yml
|
|
37
37
|
- files/context.jsonl
|
|
38
38
|
- files/context_file.txt
|
|
39
|
+
- lib/config.rb
|
|
39
40
|
- lib/context.rb
|
|
40
41
|
- lib/files.rb
|
|
41
|
-
- lib/handle_args.rb
|
|
42
42
|
- lib/help.rb
|
|
43
43
|
- lib/logging.rb
|
|
44
44
|
- lib/main.rb
|
|
45
|
-
- lib/paths.rb
|
|
46
45
|
- lib/prompt.rb
|
|
47
46
|
homepage: https://github.com/OskarFranck/terminal_chat
|
|
48
47
|
licenses:
|
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
|
data/lib/paths.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
module Files
|
|
3
|
-
def self.root
|
|
4
|
-
File.expand_path("./../", __dir__)
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
def self.context
|
|
8
|
-
File.expand_path("./../files/context.jsonl", __dir__)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def self.files
|
|
12
|
-
FILE_PATH
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.config
|
|
16
|
-
CONFIG_PATH
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def self.context_file
|
|
20
|
-
CONTEXT_FILE_PATH
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
CONTEXT_PATH = File.expand_path("./../files/context.jsonl", __dir__)
|
|
24
|
-
FILE_PATH = File.expand_path("./../files/", __dir__)
|
|
25
|
-
CONFIG_PATH = File.expand_path("./../config/config.yml", __dir__)
|
|
26
|
-
CONTEXT_FILE_PATH = File.expand_path("./../files/context_file.txt", __dir__)
|