aia 0.3.20 → 0.4.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/.semver +2 -2
- data/CHANGELOG.md +5 -0
- data/README.md +112 -87
- data/lib/aia/cli.rb +10 -5
- data/lib/aia/directives.rb +66 -0
- data/lib/aia/main.rb +94 -13
- data/lib/aia/prompt.rb +267 -0
- data/lib/aia/tools/backend_common.rb +76 -0
- data/lib/aia/tools/mods.rb +47 -118
- data/lib/aia/tools/sgpt.rb +19 -3
- data/lib/aia/tools.rb +2 -0
- data/lib/aia.rb +1 -1
- data/man/aia.1 +51 -42
- data/man/aia.1.md +59 -38
- metadata +19 -4
- data/lib/aia/prompt_processing.rb +0 -416
- data/lib/aia/tools/temp.md +0 -97
data/lib/aia/prompt.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
# lib/aia/prompt.rb
|
2
|
+
|
3
|
+
require 'reline'
|
4
|
+
|
5
|
+
class AIA::Prompt
|
6
|
+
#
|
7
|
+
# used when no prompt_id is provided but there
|
8
|
+
# are extra parameters that need to be passed
|
9
|
+
# to the backend. For example "aia -- --settings"
|
10
|
+
#
|
11
|
+
class Fake
|
12
|
+
def id = '_fake_'
|
13
|
+
def path = '_fake_'
|
14
|
+
def keywords = []
|
15
|
+
def directives = []
|
16
|
+
def to_s = ''
|
17
|
+
end
|
18
|
+
|
19
|
+
KW_HISTORY_MAX = 5
|
20
|
+
|
21
|
+
attr_reader :prompt
|
22
|
+
|
23
|
+
# setting build: false supports unit testing.
|
24
|
+
def initialize(build: true)
|
25
|
+
get_prompt
|
26
|
+
|
27
|
+
process_prompt if build
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Fetch the first argument which should be the prompt id
|
32
|
+
def get_prompt
|
33
|
+
prompt_id = AIA.config.arguments.shift
|
34
|
+
|
35
|
+
unless prompt_id
|
36
|
+
if AIA.config.extra.empty?
|
37
|
+
abort("Please provide a prompt id")
|
38
|
+
else
|
39
|
+
@prompt = Fake.new
|
40
|
+
return
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
search_for_a_matching_prompt(prompt_id) unless existing_prompt?(prompt_id)
|
45
|
+
edit_prompt if AIA.config.edit?
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Check if a prompt with the given id already exists. If so, use it.
|
50
|
+
def existing_prompt?(prompt_id)
|
51
|
+
@prompt = PromptManager::Prompt.get(id: prompt_id)
|
52
|
+
true
|
53
|
+
rescue ArgumentError
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Process the prompt's associated keywords and parameters
|
59
|
+
def process_prompt
|
60
|
+
unless @prompt.keywords.empty?
|
61
|
+
replace_keywords
|
62
|
+
@prompt.build
|
63
|
+
@prompt.save
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def replace_keywords
|
69
|
+
puts
|
70
|
+
puts "ID: #{@prompt.id}"
|
71
|
+
|
72
|
+
show_prompt_without_comments
|
73
|
+
|
74
|
+
puts "\nPress up/down arrow to scroll through history."
|
75
|
+
puts "Type new input or edit the current input."
|
76
|
+
puts "Quit #{MY_NAME} with a CNTL-D or a CNTL-C"
|
77
|
+
puts
|
78
|
+
@prompt.keywords.each do |kw|
|
79
|
+
value = keyword_value(kw, @prompt.parameters[kw])
|
80
|
+
|
81
|
+
unless value.nil? || value.strip.empty?
|
82
|
+
value_inx = @prompt.parameters[kw].index(value)
|
83
|
+
|
84
|
+
if value_inx
|
85
|
+
@prompt.parameters[kw].delete_at(value_inx)
|
86
|
+
end
|
87
|
+
|
88
|
+
# The most recent value for this kw will always be
|
89
|
+
# in the last position
|
90
|
+
@prompt.parameters[kw] << value
|
91
|
+
@prompt.parameters[kw].shift if @prompt.parameters[kw].size > KW_HISTORY_MAX
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Function to setup the Reline history with a maximum depth
|
98
|
+
def setup_reline_history(max_history_size=5)
|
99
|
+
Reline::HISTORY.clear
|
100
|
+
# Reline::HISTORY.max_size = max_history_size
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
# Function to prompt the user with a question using reline
|
105
|
+
def ask_question_with_reline(prompt)
|
106
|
+
answer = Reline.readline(prompt)
|
107
|
+
Reline::HISTORY.push(answer) unless answer.nil? || Reline::HISTORY.to_a.include?(answer)
|
108
|
+
answer
|
109
|
+
rescue Interrupt
|
110
|
+
''
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# query the user for a value to the keyword allow the
|
115
|
+
# reuse of the previous value shown as the default
|
116
|
+
#
|
117
|
+
# FIXME: Ruby v3.3.0 drops readline in favor or reline
|
118
|
+
# internally it redirects "require 'readline'" to Reline
|
119
|
+
# puts lipstick on the pig so that you can continue to
|
120
|
+
# use the Readline namespace
|
121
|
+
#
|
122
|
+
def keyword_value(kw, history_array)
|
123
|
+
setup_reline_history
|
124
|
+
|
125
|
+
default = history_array.last
|
126
|
+
|
127
|
+
Array(history_array).each { |entry| Reline::HISTORY.push(entry) unless entry.nil? || entry.empty? }
|
128
|
+
|
129
|
+
puts "Parameter #{kw} ..."
|
130
|
+
|
131
|
+
if default.empty?
|
132
|
+
user_prompt = "\n-=> "
|
133
|
+
else
|
134
|
+
user_prompt = "\n(#{default}) -=>"
|
135
|
+
end
|
136
|
+
|
137
|
+
a_string = ask_question_with_reline(user_prompt)
|
138
|
+
|
139
|
+
if a_string.nil?
|
140
|
+
puts "okay. Come back soon."
|
141
|
+
exit
|
142
|
+
end
|
143
|
+
|
144
|
+
puts
|
145
|
+
a_string.empty? ? default : a_string
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
# Search for a prompt with a matching id or keyword
|
150
|
+
def search_for_a_matching_prompt(prompt_id)
|
151
|
+
# TODO: using the rgfzf version of the search_proc should only
|
152
|
+
# return a single prompt_id
|
153
|
+
found_prompts = PromptManager::Prompt.search(prompt_id)
|
154
|
+
|
155
|
+
if found_prompts.empty?
|
156
|
+
if AIA.config.edit?
|
157
|
+
create_prompt(prompt_id)
|
158
|
+
edit_prompt
|
159
|
+
else
|
160
|
+
abort <<~EOS
|
161
|
+
|
162
|
+
No prompts where found for: #{prompt_id}
|
163
|
+
To create a prompt with this ID use the --edit option
|
164
|
+
like this:
|
165
|
+
#{MY_NAME} #{prompt_id} --edit
|
166
|
+
|
167
|
+
EOS
|
168
|
+
end
|
169
|
+
else
|
170
|
+
prompt_id = 1 == found_prompts.size ? found_prompts.first : handle_multiple_prompts(found_prompts, prompt_id)
|
171
|
+
@prompt = PromptManager::Prompt.get(id: prompt_id)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
def handle_multiple_prompts(found_these, while_looking_for_this)
|
177
|
+
raise ArgumentError, "Argument is not an Array" unless found_these.is_a?(Array)
|
178
|
+
|
179
|
+
# TODO: Make this a class constant for defaults; make the header content
|
180
|
+
# a parameter so it can be varied.
|
181
|
+
fzf_options = [
|
182
|
+
"--tabstop=2", # 2 soaces for a tab
|
183
|
+
"--header='Prompt IDs which contain: #{while_looking_for_this}\nPress ESC to cancel.'",
|
184
|
+
"--header-first",
|
185
|
+
"--prompt='Search term: '",
|
186
|
+
'--delimiter :',
|
187
|
+
"--preview 'cat $PROMPTS_DIR/{1}.txt'",
|
188
|
+
"--preview-window=down:50%:wrap"
|
189
|
+
].join(' ')
|
190
|
+
|
191
|
+
|
192
|
+
# Create a temporary file to hold the list of strings
|
193
|
+
temp_file = Tempfile.new('fzf-input')
|
194
|
+
|
195
|
+
begin
|
196
|
+
# Write all strings to the temp file
|
197
|
+
temp_file.puts(found_these)
|
198
|
+
temp_file.close
|
199
|
+
|
200
|
+
# Execute fzf command-line utility to allow selection
|
201
|
+
selected = `cat #{temp_file.path} | fzf #{fzf_options}`.strip
|
202
|
+
|
203
|
+
# Check if fzf actually returned a string; if not, return nil
|
204
|
+
result = selected.empty? ? nil : selected
|
205
|
+
ensure
|
206
|
+
# Ensure that the tempfile is closed and unlinked
|
207
|
+
temp_file.unlink
|
208
|
+
end
|
209
|
+
|
210
|
+
exit unless result
|
211
|
+
|
212
|
+
result
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
def create_prompt(prompt_id)
|
217
|
+
@prompt = PromptManager::Prompt.create(id: prompt_id)
|
218
|
+
# TODO: consider a configurable prompt template
|
219
|
+
# ERB ???
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
def edit_prompt
|
224
|
+
# FIXME: replace with the editor from the configuration
|
225
|
+
|
226
|
+
@editor = AIA::Subl.new(
|
227
|
+
file: @prompt.path
|
228
|
+
)
|
229
|
+
|
230
|
+
@editor.run # blocks until file is closed
|
231
|
+
|
232
|
+
AIA.config[:edit?] = false # turn off the --edit switch
|
233
|
+
|
234
|
+
# reload the edited prompt
|
235
|
+
@prompt = PromptManager::Prompt.get(id: @prompt.id)
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
def show_prompt_without_comments
|
240
|
+
puts remove_comments.wrap(indent: 4)
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# removes comments and directives
|
245
|
+
def remove_comments
|
246
|
+
lines = @prompt.text
|
247
|
+
.split("\n")
|
248
|
+
.reject{|a_line|
|
249
|
+
a_line.strip.start_with?('#') ||
|
250
|
+
a_line.strip.start_with?('//')
|
251
|
+
}
|
252
|
+
|
253
|
+
# Remove empty lines at the start of the prompt
|
254
|
+
#
|
255
|
+
lines = lines.drop_while(&:empty?)
|
256
|
+
|
257
|
+
# Drop all the lines at __END__ and after
|
258
|
+
#
|
259
|
+
logical_end_inx = lines.index("__END__")
|
260
|
+
|
261
|
+
if logical_end_inx
|
262
|
+
lines[0...logical_end_inx] # NOTE: ... means to not include last index
|
263
|
+
else
|
264
|
+
lines
|
265
|
+
end.join("\n")
|
266
|
+
end
|
267
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# aia/lib/aia/tools/backend_common.rb
|
2
|
+
|
3
|
+
# Used by both the AIA::Mods and AIA::Sgpt classes
|
4
|
+
|
5
|
+
module AIA::BackendCommon
|
6
|
+
attr_accessor :command, :text, :files, :parameters
|
7
|
+
|
8
|
+
def initialize(text: "", files: [])
|
9
|
+
@text = text
|
10
|
+
@files = files
|
11
|
+
@parameters = self.class::DEFAULT_PARAMETERS.dup
|
12
|
+
build_command
|
13
|
+
end
|
14
|
+
|
15
|
+
def sanitize(input)
|
16
|
+
Shellwords.escape(input)
|
17
|
+
end
|
18
|
+
def build_command
|
19
|
+
@parameters += " --model #{AIA.config.model} " if AIA.config.model
|
20
|
+
@parameters += AIA.config.extra
|
21
|
+
|
22
|
+
set_parameter_from_directives
|
23
|
+
|
24
|
+
@command = "#{meta.name} #{@parameters} "
|
25
|
+
@command += sanitize(text)
|
26
|
+
|
27
|
+
puts @command if AIA.config.debug?
|
28
|
+
|
29
|
+
@command
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_parameter_from_directives
|
33
|
+
AIA.config.directives.each do |entry|
|
34
|
+
directive, value = entry
|
35
|
+
if self.class::DIRECTIVES.include?(directive)
|
36
|
+
@parameters += " --#{directive} #{sanitize(value)}" unless @parameters.include?(directive)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
case @files.size
|
43
|
+
when 0
|
44
|
+
@result = `#{build_command}`
|
45
|
+
when 1
|
46
|
+
@result = `#{build_command} < #{@files.first}`
|
47
|
+
else
|
48
|
+
create_temp_file_with_contexts
|
49
|
+
run_with_temp_file
|
50
|
+
clean_up_temp_file
|
51
|
+
end
|
52
|
+
|
53
|
+
@result
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_temp_file_with_contexts
|
57
|
+
@temp_file = Tempfile.new("#{self.class::COMMAND_NAME}-context")
|
58
|
+
|
59
|
+
@files.each do |file|
|
60
|
+
content = File.read(file)
|
61
|
+
@temp_file.write(content)
|
62
|
+
@temp_file.write("\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
@temp_file.close
|
66
|
+
end
|
67
|
+
|
68
|
+
def run_with_temp_file
|
69
|
+
command = "#{build_command} < #{@temp_file.path}"
|
70
|
+
@result = `#{command}`
|
71
|
+
end
|
72
|
+
|
73
|
+
def clean_up_temp_file
|
74
|
+
@temp_file.unlink if @temp_file
|
75
|
+
end
|
76
|
+
end
|
data/lib/aia/tools/mods.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# lib/aia/tools/mods.rb
|
2
2
|
|
3
|
+
require_relative 'backend_common'
|
4
|
+
|
3
5
|
class AIA::Mods < AIA::Tools
|
6
|
+
include AIA::BackendCommon
|
4
7
|
|
5
8
|
meta(
|
6
9
|
name: 'mods',
|
@@ -12,141 +15,67 @@ class AIA::Mods < AIA::Tools
|
|
12
15
|
|
13
16
|
|
14
17
|
DEFAULT_PARAMETERS = [
|
18
|
+
# "--no-cache", # do not save prompt and response
|
15
19
|
"--no-limit" # no limit on input context
|
16
20
|
].join(' ').freeze
|
17
21
|
|
18
|
-
attr_accessor :command, :text, :files
|
19
|
-
|
20
|
-
|
21
|
-
def initialize(
|
22
|
-
text: "", # prompt text after keyword replacement
|
23
|
-
files: [] # context file paths (Array of Pathname)
|
24
|
-
)
|
25
|
-
|
26
|
-
@text = text
|
27
|
-
@files = files
|
28
|
-
|
29
|
-
build_command
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
def sanitize(input)
|
34
|
-
Shellwords.escape(input)
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
def build_command
|
39
|
-
parameters = DEFAULT_PARAMETERS.dup + " "
|
40
|
-
parameters += "-f " if AIA.config.markdown?
|
41
|
-
parameters += "-m #{AIA.config.model} " if AIA.config.model
|
42
|
-
parameters += AIA.config.extra
|
43
|
-
@command = "mods #{parameters} "
|
44
|
-
@command += sanitize(@text)
|
45
|
-
|
46
|
-
# context = @files.join(' ')
|
47
|
-
#
|
48
|
-
# unless context.empty?
|
49
|
-
# if @files.size > 1
|
50
|
-
# # FIXME: This syntax breaks mods which does not know how
|
51
|
-
# # to read the temporary file descriptor created
|
52
|
-
# # by the shell
|
53
|
-
# @command += " <(cat #{context})"
|
54
|
-
# else
|
55
|
-
# @command += " < #{context}"
|
56
|
-
# end
|
57
|
-
# end
|
58
|
-
|
59
|
-
@command
|
60
|
-
end
|
61
|
-
|
62
22
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# Create a temporary file that concatenates all contexts,
|
80
|
-
# to be used as STDIN for the 'mods' utility
|
81
|
-
def create_temp_file_with_contexts
|
82
|
-
@temp_file = Tempfile.new('mods-context')
|
83
|
-
|
84
|
-
@files.each do |file|
|
85
|
-
content = File.read(file)
|
86
|
-
@temp_file.write(content)
|
87
|
-
@temp_file.write("\n")
|
88
|
-
end
|
89
|
-
|
90
|
-
@temp_file.close
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
# Run 'mods' with the temporary file as STDIN
|
95
|
-
def run_mods_with_temp_file
|
96
|
-
command = "#{build_command} < #{@temp_file.path}"
|
97
|
-
@result = `#{command}`
|
98
|
-
end
|
99
|
-
|
100
|
-
|
101
|
-
# Clean up the temporary file after use
|
102
|
-
def clean_up_temp_file
|
103
|
-
@temp_file.unlink if @temp_file
|
104
|
-
end
|
23
|
+
DIRECTIVES = %w[
|
24
|
+
api
|
25
|
+
fanciness
|
26
|
+
http-proxy
|
27
|
+
max-retries
|
28
|
+
max-tokens
|
29
|
+
no-cache
|
30
|
+
no-limit
|
31
|
+
quiet
|
32
|
+
raw
|
33
|
+
status-text
|
34
|
+
temp
|
35
|
+
title
|
36
|
+
topp
|
37
|
+
]
|
105
38
|
end
|
106
39
|
|
107
40
|
__END__
|
108
41
|
|
109
42
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
43
|
##########################################################
|
114
44
|
|
115
|
-
|
116
45
|
GPT on the command line. Built for pipelines.
|
117
46
|
|
118
47
|
Usage:
|
119
48
|
mods [OPTIONS] [PREFIX TERM]
|
120
49
|
|
121
50
|
Options:
|
122
|
-
-m, --model
|
123
|
-
-a, --api
|
124
|
-
-x, --http-proxy
|
125
|
-
-f, --format
|
126
|
-
-r, --raw
|
127
|
-
-P, --prompt
|
128
|
-
-p, --prompt-args
|
129
|
-
-c, --continue
|
130
|
-
-C, --continue-last
|
131
|
-
-l, --list
|
132
|
-
-t, --title
|
133
|
-
-d, --delete
|
134
|
-
-s, --show
|
135
|
-
-S, --show-last
|
136
|
-
-q, --quiet
|
137
|
-
-h, --help
|
138
|
-
-v, --version
|
139
|
-
--max-retries
|
140
|
-
--no-limit
|
141
|
-
--max-tokens
|
142
|
-
--temp
|
143
|
-
--topp
|
144
|
-
--fanciness
|
145
|
-
--status-text
|
146
|
-
--no-cache
|
147
|
-
--reset-settings
|
148
|
-
--settings
|
149
|
-
--dirs
|
51
|
+
-m, --model Default model (gpt-3.5-turbo, gpt-4, ggml-gpt4all-j...).
|
52
|
+
-a, --api OpenAI compatible REST API (openai, localai).
|
53
|
+
-x, --http-proxy HTTP proxy to use for API requests.
|
54
|
+
-f, --format Ask for the response to be formatted as markdown unless otherwise set.
|
55
|
+
-r, --raw Render output as raw text when connected to a TTY.
|
56
|
+
-P, --prompt Include the prompt from the arguments and stdin, truncate stdin to specified number of lines.
|
57
|
+
-p, --prompt-args Include the prompt from the arguments in the response.
|
58
|
+
-c, --continue Continue from the last response or a given save title.
|
59
|
+
-C, --continue-last Continue from the last response.
|
60
|
+
-l, --list Lists saved conversations.
|
61
|
+
-t, --title Saves the current conversation with the given title.
|
62
|
+
-d, --delete Deletes a saved conversation with the given title or ID.
|
63
|
+
-s, --show Show a saved conversation with the given title or ID.
|
64
|
+
-S, --show-last Show a the last saved conversation.
|
65
|
+
-q, --quiet Quiet mode (hide the spinner while loading and stderr messages for success).
|
66
|
+
-h, --help Show help and exit.
|
67
|
+
-v, --version Show version and exit.
|
68
|
+
--max-retries Maximum number of times to retry API calls.
|
69
|
+
--no-limit Turn off the client-side limit on the size of the input into the model.
|
70
|
+
--max-tokens Maximum number of tokens in response.
|
71
|
+
--temp Temperature (randomness) of results, from 0.0 to 2.0.
|
72
|
+
--topp TopP, an alternative to temperature that narrows response, from 0.0 to 1.0.
|
73
|
+
--fanciness Your desired level of fanciness.
|
74
|
+
--status-text Text to show while generating.
|
75
|
+
--no-cache Disables caching of the prompt/response.
|
76
|
+
--reset-settings Backup your old settings file and reset everything to the defaults.
|
77
|
+
--settings Open settings in your $EDITOR.
|
78
|
+
--dirs Print the directories in which mods store its data
|
150
79
|
|
151
80
|
Example:
|
152
81
|
# Editorialize your video files
|
data/lib/aia/tools/sgpt.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# lib/aia/tools/sgpt.rb
|
2
2
|
|
3
|
+
require_relative 'backend_common'
|
4
|
+
|
3
5
|
class AIA::Sgpt < AIA::Tools
|
6
|
+
include AIA::BackendCommon
|
4
7
|
|
5
8
|
meta(
|
6
9
|
name: 'sgpt',
|
@@ -10,9 +13,22 @@ class AIA::Sgpt < AIA::Tools
|
|
10
13
|
install: "pip install shell-gpt",
|
11
14
|
)
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
|
17
|
+
DEFAULT_PARAMETERS = [
|
18
|
+
# "--verbose", # enable verbose logging (if applicable)
|
19
|
+
# Add default parameters here
|
20
|
+
].join(' ').freeze
|
21
|
+
|
22
|
+
DIRECTIVES = %w[
|
23
|
+
model
|
24
|
+
temperature
|
25
|
+
max_tokens
|
26
|
+
top_p
|
27
|
+
frequency_penalty
|
28
|
+
presence_penalty
|
29
|
+
stop_sequence
|
30
|
+
api_key
|
31
|
+
]
|
16
32
|
end
|
17
33
|
|
18
34
|
__END__
|
data/lib/aia/tools.rb
CHANGED