aia 0.3.20 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,416 +0,0 @@
1
- # lib/aia/prompt_processing.rb
2
-
3
- module AIA::PromptProcessing
4
- KW_HISTORY_MAX = 5
5
-
6
- # Fetch the first argument which should be the prompt id
7
- def get_prompt
8
- prompt_id = AIA.config.arguments.shift
9
-
10
- # TODO: or maybe go to a generic search and select process
11
-
12
- # TODO: if external options were provided but no prompt id
13
- # then by pass prompt process and send the extra
14
- # options to the backend. For example:
15
- # aia -- --settings
16
- # should send --settings to mods and nothing else
17
-
18
- abort("Please provide a prompt id") unless prompt_id
19
-
20
- search_for_a_matching_prompt(prompt_id) unless existing_prompt?(prompt_id)
21
- edit_prompt if AIA.config.edit?
22
- end
23
-
24
-
25
- # Check if a prompt with the given id already exists
26
- def existing_prompt?(prompt_id)
27
- @prompt = PromptManager::Prompt.get(id: prompt_id)
28
-
29
- # FIXME: Kludge until prompt_manager is changed
30
- # prompt_manager v0.3.0 now supports this feature.
31
- # keeping the kludge in for legacy JSON files
32
- # files which have not yet been reformatted.
33
- @prompt.keywords.each do |kw|
34
- if @prompt.parameters[kw].nil? || @prompt.parameters[kw].empty?
35
- @prompt.parameters[kw] = []
36
- else
37
- @prompt.parameters[kw] = Array(@prompt.parameters[kw])
38
- end
39
- end
40
-
41
- true
42
- rescue ArgumentError
43
- false
44
- end
45
-
46
-
47
- # Process the prompt's associated keywords and parameters
48
- def process_prompt
49
- unless @prompt.keywords.empty?
50
- replace_keywords
51
- @prompt.build
52
- @prompt.save
53
- end
54
- end
55
-
56
-
57
-
58
- def replace_keywords
59
- puts
60
- puts "ID: #{@prompt.id}"
61
-
62
- show_prompt_without_comments
63
-
64
- puts "\nPress up/down arrow to scroll through history."
65
- puts "Type new input or edit the current input."
66
- puts "Quit #{MY_NAME} with a CNTL-D or a CNTL-C"
67
- puts
68
- @prompt.keywords.each do |kw|
69
- value = keyword_value(kw, @prompt.parameters[kw])
70
-
71
- unless value.nil? || value.strip.empty?
72
- value_inx = @prompt.parameters[kw].index(value)
73
-
74
- if value_inx
75
- @prompt.parameters[kw].delete_at(value_inx)
76
- end
77
-
78
- # The most recent value for this kw will always be
79
- # in the last position
80
- @prompt.parameters[kw] << value
81
- @prompt.parameters[kw].shift if @prompt.parameters[kw].size > KW_HISTORY_MAX
82
- end
83
- end
84
- end
85
-
86
-
87
- # query the user for a value to the keyword allow the
88
- # reuse of the previous value shown as the default
89
- def keyword_value(kw, history_array)
90
-
91
- Readline::HISTORY.clear
92
- Array(history_array).each { |entry| Readline::HISTORY.push(entry) unless entry.nil? || entry.empty? }
93
-
94
- puts "Parameter #{kw} ..."
95
-
96
- begin
97
- a_string = Readline.readline("\n-=> ", true)
98
- rescue Interrupt
99
- a_string = nil
100
- end
101
-
102
- if a_string.nil?
103
- puts "okay. Come back soon."
104
- exit
105
- end
106
-
107
- puts
108
- a_string.empty? ? default : a_string
109
- end
110
-
111
-
112
- # Search for a prompt with a matching id or keyword
113
- def search_for_a_matching_prompt(prompt_id)
114
- # TODO: using the rgfzf version of the search_proc should only
115
- # return a single prompt_id
116
- found_prompts = PromptManager::Prompt.search(prompt_id)
117
-
118
- if found_prompts.empty?
119
- if edit?
120
- create_prompt(prompt_id)
121
- edit_prompt
122
- else
123
- abort <<~EOS
124
-
125
- No prompts where found for: #{prompt_id}
126
- To create a prompt with this ID use the --edit option
127
- like this:
128
- #{MY_NAME} #{prompt_id} --edit
129
-
130
- EOS
131
- end
132
- else
133
- prompt_id = 1 == found_prompts.size ? found_prompts.first : handle_multiple_prompts(found_prompts, prompt_id)
134
- @prompt = PromptManager::Prompt.get(id: prompt_id)
135
- end
136
- end
137
-
138
-
139
- def handle_multiple_prompts(found_these, while_looking_for_this)
140
- raise ArgumentError, "Argument is not an Array" unless found_these.is_a?(Array)
141
-
142
- # TODO: Make this a class constant for defaults; make the header content
143
- # a parameter so it can be varied.
144
- fzf_options = [
145
- "--tabstop=2", # 2 soaces for a tab
146
- "--header='Prompt IDs which contain: #{while_looking_for_this}\nPress ESC to cancel.'",
147
- "--header-first",
148
- "--prompt='Search term: '",
149
- '--delimiter :',
150
- "--preview 'cat $PROMPTS_DIR/{1}.txt'",
151
- "--preview-window=down:50%:wrap"
152
- ].join(' ')
153
-
154
-
155
- # Create a temporary file to hold the list of strings
156
- temp_file = Tempfile.new('fzf-input')
157
-
158
- begin
159
- # Write all strings to the temp file
160
- temp_file.puts(found_these)
161
- temp_file.close
162
-
163
- # Execute fzf command-line utility to allow selection
164
- selected = `cat #{temp_file.path} | fzf #{fzf_options}`.strip
165
-
166
- # Check if fzf actually returned a string; if not, return nil
167
- result = selected.empty? ? nil : selected
168
- ensure
169
- # Ensure that the tempfile is closed and unlinked
170
- temp_file.unlink
171
- end
172
-
173
- exit unless result
174
-
175
- result
176
- end
177
-
178
-
179
- def create_prompt(prompt_id)
180
- @prompt = PromptManager::Prompt.create(id: prompt_id)
181
- # TODO: consider a configurable prompt template
182
- # ERB ???
183
- end
184
-
185
-
186
- def edit_prompt
187
- # FIXME: replace with the editor from the configuration
188
-
189
- @editor = AIA::Subl.new(
190
- file: @prompt.path
191
- )
192
-
193
- @editor.run # blocks until file is closed
194
-
195
- @options[:edit?][0] = false # turn off the --edit switch
196
-
197
- # reload the edited prompt
198
- @prompt = PromptManager::Prompt.get(id: @prompt.id)
199
- end
200
-
201
-
202
- def show_prompt_without_comments
203
- puts remove_comments.wrap(indent: 4)
204
- end
205
-
206
-
207
- def remove_comments
208
- lines = @prompt.text
209
- .split("\n")
210
- .reject{|a_line| a_line.strip.start_with?('#')}
211
-
212
- # Remove empty lines at the start of the prompt
213
- #
214
- lines = lines.drop_while(&:empty?)
215
-
216
- # Drop all the lines at __END__ and after
217
- #
218
- logical_end_inx = lines.index("__END__")
219
-
220
- if logical_end_inx
221
- lines[0...logical_end_inx] # NOTE: ... means to not include last index
222
- else
223
- lines
224
- end.join("\n")
225
- end
226
- end
227
-
228
-
229
-
230
- __END__
231
-
232
- # lib/aia/prompt_processing.rb
233
-
234
- class AIA::PromptProcessing
235
- KW_HISTORY_MAX = 5
236
-
237
- def initialize(arguments, options)
238
- @arguments = arguments
239
- @options = options
240
- @prompt = nil
241
- end
242
-
243
- def execute
244
- get_prompt
245
- process_prompt
246
- end
247
-
248
- private
249
-
250
- def get_prompt
251
- prompt_id = @arguments.shift
252
- abort("Please provide a prompt id") unless prompt_id
253
- search_for_a_matching_prompt(prompt_id) unless existing_prompt?(prompt_id)
254
- edit_prompt if edit?
255
- end
256
-
257
- def existing_prompt?(prompt_id)
258
- @prompt = PromptManager::Prompt.get(id: prompt_id)
259
- @prompt.keywords.each do |kw|
260
- @prompt.parameters[kw] = Array(@prompt.parameters[kw])
261
- end
262
- true
263
- rescue ArgumentError
264
- false
265
- end
266
-
267
- def process_prompt
268
- return if @prompt.keywords.empty?
269
- replace_keywords
270
- @prompt.build
271
- @prompt.save
272
- end
273
-
274
- def replace_keywords
275
- puts "ID: #{@prompt.id}"
276
- show_prompt_without_comments
277
- puts_instructions
278
- @prompt.keywords.each do |kw|
279
- value = keyword_value(kw, @prompt.parameters[kw])
280
- update_keyword_history(kw, value) unless value.nil? || value.strip.empty?
281
- end
282
- end
283
-
284
- def puts_instructions
285
- puts "\nPress up/down arrow to scroll through history."
286
- puts "Type new input or edit the current input."
287
- puts "Quit #{MY_NAME} with a CNTL-D or a CNTL-C"
288
- puts
289
- end
290
-
291
- def update_keyword_history(kw, value)
292
- params = @prompt.parameters[kw]
293
- params.delete(value)
294
- params << value
295
- params.shift if params.size > KW_HISTORY_MAX
296
- end
297
-
298
- def keyword_value(kw, history_array)
299
- Readline::HISTORY.clear
300
- Array(history_array).each { |entry| Readline::HISTORY.push(entry) unless entry.nil? || entry.empty? }
301
- puts "Parameter #{kw} ..."
302
- begin
303
- a_string = Readline.readline("\n-=> ", true)
304
- rescue Interrupt
305
- a_string = nil
306
- end
307
- abort("okay. Come back soon.") if a_string.nil?
308
- a_string.empty? ? history_array.first : a_string
309
- end
310
-
311
- def search_for_a_matching_prompt(prompt_id)
312
- found_prompts = PromptManager::Prompt.search(prompt_id)
313
- handle_no_prompts_found(prompt_id) if found_prompts.empty?
314
- prompt_id = found_prompts.size == 1 ? found_prompts.first : handle_multiple_prompts(found_prompts, prompt_id)
315
- @prompt = PromptManager::Prompt.get(id: prompt_id)
316
- end
317
-
318
- def handle_no_prompts_found(prompt_id)
319
- if edit?
320
- create_prompt(prompt_id)
321
- edit_prompt
322
- else
323
- abort_no_prompts_error(prompt_id)
324
- end
325
- end
326
-
327
- def abort_no_prompts_error(prompt_id)
328
- abort <<~EOS
329
-
330
- No prompts were found for: #{prompt_id}
331
- To create a prompt with this ID use the --edit option
332
- like this:
333
- #{MY_NAME} #{prompt_id} --edit
334
-
335
- EOS
336
- end
337
-
338
- def handle_multiple_prompts(found_these, while_looking_for_this)
339
- raise ArgumentError, "Argument is not an Array" unless found_these.is_a?(Array)
340
- result = execute_fzf(found_these, while_looking_for_this)
341
- abort unless result
342
- result
343
- end
344
-
345
- def execute_fzf(found_these, while_looking_for_this)
346
- fzf_options = build_fzf_options(while_looking_for_this)
347
- temp_file = create_tempfile_with_entries(found_these)
348
- selected = `cat #{temp_file.path} | fzf #{fzf_options}`.strip
349
- temp_file.unlink
350
- selected.empty? ? nil : selected
351
- end
352
-
353
- def build_fzf_options(search_term)
354
- [
355
- "--tabstop=2",
356
- "--header='Prompt IDs which contain: #{search_term}\nPress ESC to cancel.'",
357
- "--header-first",
358
- "--prompt='Search term: '",
359
- '--delimiter :',
360
- "--preview 'cat $PROMPTS_DIR/{1}.txt'",
361
- "--preview-window=down:50%:wrap"
362
- ].join(' ')
363
- end
364
-
365
- def create_tempfile_with_entries(entries)
366
- temp_file = Tempfile.new('fzf-input')
367
- temp_file.puts(entries)
368
- temp_file.close
369
- temp_file
370
- end
371
-
372
- def create_prompt(prompt_id)
373
- @prompt = PromptManager::Prompt.create(id: prompt_id)
374
- # Additional prompt config...
375
- end
376
-
377
-
378
-
379
- def edit_prompt
380
- # FIXME: replace with the editor from the configuration
381
-
382
- @editor = AIA::Subl.new(
383
- file: @prompt.path
384
- )
385
-
386
- @editor.run # blocks until file is closed
387
-
388
- @options[:edit?][0] = false # turn off the --edit switch
389
-
390
- # reload the edited prompt
391
- @prompt = PromptManager::Prompt.get(id: @prompt.id)
392
- end
393
-
394
-
395
- def show_prompt_without_comments
396
- puts remove_comments.wrap(indent: 4)
397
- end
398
-
399
- def remove_comments
400
- lines = @prompt.text.lines
401
- .reject { |a_line| a_line.strip.start_with?('#') }
402
- .drop_while(&:empty?)
403
- logical_end_inx = lines.index("__END__")
404
- lines = lines[0...logical_end_inx] if logical_end_inx
405
- lines.join("\n")
406
- end
407
-
408
- def edit?
409
- @options[:edit?] && @options[:edit?][0] == true
410
- end
411
- end
412
-
413
-
414
-
415
-
416
-
@@ -1,97 +0,0 @@
1
- # lib/aia/tools.rb
2
-
3
- ```ruby
4
- require 'hashie'
5
-
6
- module AIA
7
- class Tools
8
- @subclasses = {}
9
-
10
- class << self
11
- attr_reader :subclasses, :metadata
12
-
13
- def inherited(subclass)
14
- @subclasses[subclass.name.split('::').last.downcase] = subclass
15
- subclass.instance_variable_set(:@metadata, Jashie::Mash.new)
16
- end
17
-
18
- def meta
19
- @metadata ||= Jashie::Mash.new
20
- end
21
-
22
- def define_metadata(&block)
23
- meta.instance_eval(&block)
24
- end
25
-
26
- def search_for(name: nil, role: nil)
27
- return subclasses[name.downcase] if name
28
- return subclasses.values.select { |subclass| subclass.meta.role == role } if role
29
- end
30
- end
31
-
32
- def self.method_missing(name, *args, &block)
33
- @metadata.public_send(name, *args, &block)
34
- end
35
-
36
- def self.respond_to_missing?(method_name, include_private = false)
37
- @metadata.respond_to?(method_name) || super
38
- end
39
- end
40
- end
41
- ```
42
-
43
- # lib/aia/tools/mods.rb
44
-
45
- ```ruby
46
- require_relative 'tools'
47
-
48
- module AIA
49
- class Mods < Tools
50
- DEFAULT_PARAMETERS = "--no-limit".freeze
51
-
52
- attr_accessor :command, :extra_options, :text, :files
53
-
54
- define_metadata do
55
- role :backend
56
- desc 'AI on the command-line'
57
- url 'https://github.com/charmbracelet/mods'
58
- end
59
-
60
- def initialize(extra_options: "", text: "", files: [])
61
- @extra_options = extra_options
62
- @text = text
63
- @files = files
64
- build_command
65
- end
66
-
67
- def build_command
68
- parameters = DEFAULT_PARAMETERS.dup + " "
69
- parameters += "-f " if ::AIA.config.markdown?
70
- parameters += "-m #{AIA.config.model} " if ::AIA.config.model
71
- parameters += @extra_options
72
- @command = "mods #{parameters}"
73
- @command += %Q["#{@text}"]
74
-
75
- @files.each { |f| @command += " < #{f}" }
76
-
77
- @command
78
- end
79
-
80
- def run
81
- `#{@command}`
82
- end
83
- end
84
- end
85
- ```
86
-
87
- ```ruby
88
- # Example usage:
89
- # mods_class = AIA::Tools.search_for(name: 'mods')
90
- # mods_instance = mods_class.new(text: "Hello, mods!")
91
- # result = mods_instance.run
92
-
93
- # backend_tools = AIA::Tools.search_for(role: :backend)
94
- ```
95
-
96
- Note: The `Jashie::Mash` class is assumed to behave like `Hashie::Mash` (or similar) in providing a flexible object for storing metadata. You'll need to define `Jashie::Mash` or import a library that provides a similar functionality to match this example.
97
-
@@ -1,126 +0,0 @@
1
- ## Suggested Refactoring into Modules
2
-
3
- ### ConfigurationModule
4
-
5
- This module could encapsulate all the constants and environment-dependent settings.
6
-
7
- ```ruby
8
- module Configuration
9
- HOME = Pathname.new(ENV['HOME'])
10
- PROMPTS_DIR = Pathname.new(ENV['PROMPTS_DIR'] || (HOME + ".prompts_dir"))
11
- AI_CLI_PROGRAM = "mods"
12
- EDITOR = ENV['EDITOR'] || 'edit'
13
- MY_NAME = Pathname.new(__FILE__).basename.to_s.split('.')[0]
14
- MODS_MODEL = ENV['MODS_MODEL'] || 'gpt-4-1106-preview'
15
- OUTPUT = Pathname.pwd + "temp.md"
16
- PROMPT_LOG = PROMPTS_DIR + "_prompts.log"
17
- USAGE = <<~EOUSAGE
18
- AI Assistant (aia)
19
- ==================
20
- The AI cli program being used is: #{AI_CLI_PROGRAM}
21
- You can pass additional CLI options to #{AI_CLI_PROGRAM} like this:
22
- "#{MY_NAME} my options -- options for #{AI_CLI_PROGRAM}"
23
- EOUSAGE
24
- end
25
- ```
26
-
27
- ### OptionParsingModule
28
-
29
- This module could manage the parsing of command-line arguments and configuring the options for the application.
30
-
31
- ```ruby
32
- module OptionParsing
33
- def build_reader_methods
34
- # ... method definition ...
35
- end
36
-
37
- def process_arguments
38
- # ... method definition ...
39
- end
40
-
41
- def check_for(an_option)
42
- # ... method definition ...
43
- end
44
-
45
- def process_option(option_sym, switches)
46
- # ... method definition ...
47
- end
48
- end
49
- ```
50
-
51
- ### CommandLineInterfaceModule
52
-
53
- This module would manage interactions with the command-line interface including editing of prompts and selection processes.
54
-
55
- ```ruby
56
- module CommandLineInterface
57
- def keyword_value(kw, default)
58
- # ... method definition ...
59
- end
60
-
61
- def handle_multiple_prompts(found_these, while_looking_for_this)
62
- # ... method definition ...
63
- end
64
- end
65
- ```
66
-
67
- ### LoggingModule
68
-
69
- Responsible for logging the results of the command.
70
-
71
- ```ruby
72
- module Logging
73
- def write_to_log(answer)
74
- # ... method definition ...
75
- end
76
- end
77
- ```
78
-
79
- ### AICommandModule
80
-
81
- Manages the building and execution of the AI CLI command.
82
-
83
- ```ruby
84
- module AICommand
85
- def setup_cli_program
86
- # ... method definition ...
87
- end
88
-
89
- def build_command
90
- # ... method definition ...
91
- end
92
-
93
- def execute_and_log_command(command)
94
- # ... method definition ...
95
- end
96
- end
97
- ```
98
-
99
- ### PromptProcessingModule
100
-
101
- Handles prompt retrieval, existing check, and keyword processing.
102
-
103
- ```ruby
104
- module PromptProcessing
105
- def existing_prompt?(prompt_id)
106
- # ... method definition ...
107
- end
108
-
109
- def process_prompt
110
- # ... method definition ...
111
- end
112
-
113
- def replace_keywords
114
- # ... method definition ...
115
- end
116
-
117
- def search_for_a_matching_prompt(prompt_id)
118
- # ... method definition ...
119
- end
120
- end
121
- ```
122
-
123
- Each module should only contain the methods relevant to that module's purpose. After defining these modules, they can be included in the `AIA::Main` class where appropriate. Note that the method `get_prompt_id` didn't fit neatly into one of the outlined modules; it may remain in the main class or be included in a module if additional context becomes available or if it can be logically grouped with similar methods.
124
-
125
- The `__END__` block and the Readline history management could be encapsulated into a separate module for terminal interactions if that block grows in complexity or moves out of the overall class definition.
126
-