prompt_manager 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/Rakefile +1 -1
- data/{lib → examples}/aip.rb +98 -79
- data/examples/prompts_dir/todo.json +1 -0
- data/examples/prompts_dir/todo.txt +8 -0
- data/lib/prompt_manager/prompt.rb +153 -0
- data/lib/prompt_manager/storage/active_record_adapter.rb +66 -0
- data/lib/prompt_manager/storage/file_system_adapter.rb +149 -0
- data/lib/prompt_manager/storage/sqlite_adapter.rb +65 -0
- data/lib/prompt_manager/storage.rb +7 -0
- data/lib/prompt_manager/version.rb +1 -1
- data/lib/prompt_manager.rb +7 -1
- metadata +11 -5
- data/lib/temp.md +0 -57
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74f0d17c5e89ae990b91f2cc8632751ecb1664f1dfc248de9967828f088101b3
|
4
|
+
data.tar.gz: 47e60cd381bf459a34b1a6a6350cd0ca600125677535321d36e2d6d8395fcd2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5033e585f72c925f0ca3461288d7429362a184567eb6713676ba213ac91e6dcced8ae48475346bf0a2b411890b143b7ad22c35ca4e8f3be0f4f4a08b9685db0
|
7
|
+
data.tar.gz: 72b4b277108805d56c7322c862eba84eff11379ba7c3efb116b9276de96ab8de77726c67b102a008bac5046fa697854ba3274bb6d0d15782d1474ae07d6a4945
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# PromptManager
|
2
2
|
|
3
|
-
**Under Development**
|
3
|
+
**Under Development** Not ready for use.
|
4
|
+
|
5
|
+
I'm looking for some contributors to help define the API between the Prompt class and the Storage adapters. I'm focusing on the FileSystemAdapter since the majority of my work is on the command line.
|
4
6
|
|
5
7
|
Extracting the prompt management functionality fro the aip.rb file into a new gem that will provide a generic management service for other programs.
|
6
8
|
|
data/Rakefile
CHANGED
data/{lib → examples}/aip.rb
RENAMED
@@ -13,6 +13,9 @@
|
|
13
13
|
#
|
14
14
|
|
15
15
|
|
16
|
+
require_relative '../lib/prompt_manager'
|
17
|
+
|
18
|
+
|
16
19
|
=begin
|
17
20
|
|
18
21
|
brew install fzf mods the_silver_searcher
|
@@ -48,9 +51,11 @@ This robust tool is excellent for users who wish to harness the power of generat
|
|
48
51
|
|
49
52
|
#
|
50
53
|
# TODO: I think this script has reached the point where
|
51
|
-
# it is ready to become a proper gem.
|
54
|
+
# it is ready to become a proper gem. This would
|
55
|
+
# be a different gem than prompt_manager.
|
52
56
|
#
|
53
57
|
|
58
|
+
|
54
59
|
require 'pathname'
|
55
60
|
HOME = Pathname.new( ENV['HOME'] )
|
56
61
|
|
@@ -72,22 +77,10 @@ AI_COMMAND = "#{AI_CLI_PROGRAM} #{ai_options} "
|
|
72
77
|
EDITOR = ENV['EDITOR']
|
73
78
|
PROMPT_DIR = HOME + ".prompts"
|
74
79
|
PROMPT_LOG = PROMPT_DIR + "_prompts.log"
|
75
|
-
PROMPT_EXTNAME = ".txt"
|
76
|
-
DEFAULTS_EXTNAME = ".json"
|
77
|
-
# SEARCH_COMMAND = "ag -l"
|
78
|
-
KEYWORD_REGEX = /(\[[A-Z _|]+\])/
|
79
80
|
|
80
|
-
AVAILABLE_PROMPTS = PROMPT_DIR
|
81
|
-
.children
|
82
|
-
.select{|c| PROMPT_EXTNAME == c.extname}
|
83
|
-
.map{|c| c.basename.to_s.split('.')[0]}
|
84
|
-
|
85
|
-
AVAILABLE_PROMPTS_HELP = AVAILABLE_PROMPTS
|
86
|
-
.map{|c| " * " + c}
|
87
|
-
.join("\n")
|
88
81
|
|
89
82
|
require 'amazing_print'
|
90
|
-
require 'json'
|
83
|
+
# require 'json'
|
91
84
|
require 'readline' # TODO: or reline ??
|
92
85
|
require 'word_wrap'
|
93
86
|
require 'word_wrap/core_ext'
|
@@ -125,6 +118,7 @@ cli_helper("Use generative AI with saved parameterized prompts") do |o|
|
|
125
118
|
o.bool '-e', '--edit', 'Edit the prompt text', default: false
|
126
119
|
o.bool '-f', '--fuzzy', 'Allow fuzzy matching', default: false
|
127
120
|
o.path '-o', '--output', 'The output file', default: Pathname.pwd + "temp.md"
|
121
|
+
o.string '-s', '--storage','How are the prompts stored', default: 'FileSystemAdapter'
|
128
122
|
end
|
129
123
|
|
130
124
|
|
@@ -173,60 +167,84 @@ unless edit?
|
|
173
167
|
end
|
174
168
|
end
|
175
169
|
|
170
|
+
valid_storage = %w[
|
171
|
+
FileSystemAdapter
|
172
|
+
SqliteAdapter
|
173
|
+
ActiveRecordAdapter
|
174
|
+
]
|
175
|
+
|
176
|
+
if valid_storange.include? configatron.storage
|
177
|
+
require_relative "../lib/storage/#{configatron.storage}"
|
178
|
+
STORAGE = "PromptManager::Storage::#{configatron.storage}".constantize
|
179
|
+
else
|
180
|
+
error "Unknow storage: #{configatron.storage}"
|
181
|
+
end
|
182
|
+
|
176
183
|
abort_if_errors
|
177
184
|
|
178
|
-
configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
|
179
|
-
configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
|
185
|
+
# configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
|
186
|
+
# configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
|
180
187
|
|
181
|
-
|
182
|
-
|
188
|
+
begin
|
189
|
+
PromptManager::Prompt.storage_adapter = STORAGE.new
|
190
|
+
rescue StandardError => e
|
191
|
+
error "Unknown storage adapter: #{configatron.storage}\n#{e}"
|
183
192
|
end
|
184
193
|
|
185
|
-
configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
|
186
|
-
configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
|
187
|
-
|
188
194
|
abort_if_errors
|
189
195
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
196
|
+
begin
|
197
|
+
PROMPT = PromptManager::Prompt.get(id: configatron.prompt)
|
198
|
+
rescue Exception => e
|
199
|
+
error "Storage (#{configatron.storage}) does not have: #{configatron.prompt}\n#{e}"
|
200
|
+
end
|
195
201
|
|
196
|
-
|
197
|
-
end
|
202
|
+
abort_if_errors
|
198
203
|
|
199
|
-
|
200
|
-
|
204
|
+
# TODO: This will have to change.
|
205
|
+
#
|
206
|
+
# if edit?
|
207
|
+
# unless configatron.prompt_path.exist?
|
208
|
+
# configatron.prompt_path.write <<~PROMPT
|
209
|
+
# # #{configatron.prompt_path.relative_path_from(HOME)}
|
210
|
+
# # DESC:
|
211
|
+
#
|
212
|
+
# PROMPT
|
213
|
+
# end
|
214
|
+
#
|
215
|
+
# `#{EDITOR} #{configatron.prompt_path}`
|
216
|
+
# end
|
201
217
|
|
202
218
|
######################################################
|
203
219
|
# Local methods
|
204
220
|
|
205
|
-
def extract_raw_prompt
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
end
|
214
|
-
|
215
|
-
|
216
|
-
def ignore_after_end
|
217
|
-
array_of_strings = configatron.prompt_path.readlines
|
218
|
-
.map{|a_line| a_line.chomp.strip}
|
219
|
-
|
220
|
-
x = array_of_strings.index("__END__")
|
221
|
+
# def extract_raw_prompt
|
222
|
+
# array_of_strings = ignore_after_end
|
223
|
+
# print_header_comment(array_of_strings)
|
224
|
+
#
|
225
|
+
# array_of_strings.reject do |a_line|
|
226
|
+
# a_line.chomp.strip.start_with?('#')
|
227
|
+
# end
|
228
|
+
# .join("\n")
|
229
|
+
# end
|
221
230
|
|
222
|
-
unless x.nil?
|
223
|
-
array_of_strings = array_of_strings[..x-1]
|
224
|
-
end
|
225
231
|
|
226
|
-
|
227
|
-
|
232
|
+
# def ignore_after_end
|
233
|
+
# array_of_strings = configatron.prompt_path.readlines
|
234
|
+
# .map{|a_line| a_line.chomp.strip}
|
235
|
+
#
|
236
|
+
# x = array_of_strings.index("__END__")
|
237
|
+
#
|
238
|
+
# unless x.nil?
|
239
|
+
# array_of_strings = array_of_strings[..x-1]
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
# array_of_strings
|
243
|
+
# end
|
228
244
|
|
229
245
|
|
246
|
+
# SMELL: This is based upon a convention that not everyone
|
247
|
+
# may want to follow.
|
230
248
|
def print_header_comment(array_of_strings)
|
231
249
|
print "\n\n" if verbose?
|
232
250
|
|
@@ -247,9 +265,9 @@ end
|
|
247
265
|
# [KEY PHRASE]
|
248
266
|
# [KEY PHRASE | KEY PHRASE | KEY_WORD]
|
249
267
|
#
|
250
|
-
def extract_keywords_from(prompt_raw)
|
251
|
-
|
252
|
-
end
|
268
|
+
# def extract_keywords_from(prompt_raw)
|
269
|
+
# prompt_raw.scan(KEYWORD_REGEX).flatten.uniq
|
270
|
+
# end
|
253
271
|
|
254
272
|
# get the replacements for the keywords
|
255
273
|
def replacements_for(keywords)
|
@@ -268,31 +286,32 @@ def replacements_for(keywords)
|
|
268
286
|
end
|
269
287
|
|
270
288
|
|
271
|
-
def load_default_replacements
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
end
|
278
|
-
|
289
|
+
# def load_default_replacements
|
290
|
+
# if configatron.defaults_path.exist?
|
291
|
+
# JSON.parse(configatron.defaults_path.read)
|
292
|
+
# else
|
293
|
+
# {}
|
294
|
+
# end
|
295
|
+
# end
|
279
296
|
|
280
|
-
def save_default_replacements(a_hash)
|
281
|
-
return if a_hash.empty?
|
282
|
-
defaults = a_hash.to_json
|
283
|
-
configatron.defaults_path.write defaults
|
284
|
-
end
|
285
297
|
|
286
|
-
#
|
287
|
-
|
288
|
-
|
298
|
+
# def save_default_replacements(a_hash)
|
299
|
+
# return if a_hash.empty?
|
300
|
+
# defaults = a_hash.to_json
|
301
|
+
# configatron.defaults_path.write defaults
|
302
|
+
# end
|
289
303
|
|
290
|
-
replacements.each_pair do |keyword, replacement|
|
291
|
-
prompt.gsub!(keyword, replacement)
|
292
|
-
end
|
293
304
|
|
294
|
-
|
295
|
-
|
305
|
+
# substitute the replacements for the keywords
|
306
|
+
# def replace_keywords_with replacements, prompt_raw
|
307
|
+
# prompt = prompt_raw.dup
|
308
|
+
#
|
309
|
+
# replacements.each_pair do |keyword, replacement|
|
310
|
+
# prompt.gsub!(keyword, replacement)
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# prompt
|
314
|
+
# end
|
296
315
|
|
297
316
|
|
298
317
|
def log(prompt_path, prompt_raw, answer)
|
@@ -323,16 +342,16 @@ end
|
|
323
342
|
|
324
343
|
ap configatron.to_h if debug?
|
325
344
|
|
326
|
-
configatron.prompt_raw = extract_raw_prompt
|
345
|
+
# configatron.prompt_raw = extract_raw_prompt
|
327
346
|
|
328
347
|
puts
|
329
348
|
puts "PROMPT:"
|
330
|
-
puts
|
349
|
+
puts prompt.raw_text.wrap
|
331
350
|
puts
|
332
351
|
|
333
352
|
|
334
|
-
keywords =
|
335
|
-
replacements =
|
353
|
+
keywords = PROMPT.parameters.keys
|
354
|
+
replacements = PROMPT.parameters
|
336
355
|
|
337
356
|
prompt = replace_keywords_with replacements, configatron.prompt_raw
|
338
357
|
ptompt = %Q{prompt}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"language":"ruby"}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# .prompts/todo.txt
|
2
|
+
# Desc: replace NotImpleted.needs_to "prompt_string" with the
|
3
|
+
# results of sending the prompt_string to an LLM
|
4
|
+
#
|
5
|
+
|
6
|
+
As an experienced [LANGUAGE] software engineer write some [LANGUAGE] source code. Consider the following [LANGUAGE] file. For each comment line that contains the word "todo" take the text that follows that word as a requirement to be implemented in [LANGUAGE]. Remove the 'todo' word from the comment line. After the line insert the [LANGUAGE] code that implements the requirement.
|
7
|
+
|
8
|
+
__END__
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# prompt_manager/lib/prompt_manager/prompt.rb
|
2
|
+
|
3
|
+
# TODO: Consider an ActiveModel ??
|
4
|
+
|
5
|
+
class PromptManager::Prompt
|
6
|
+
PARAMETER_REGEX = /\[([A-Z _]+)\]/.freeze
|
7
|
+
# KEYWORD_REGEX = /(\[[A-Z _|]+\])/ # NOTE: old from aip.rb
|
8
|
+
@storage_adapter = nil
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :storage_adapter
|
12
|
+
|
13
|
+
alias_method :get, :new
|
14
|
+
|
15
|
+
def create(id:, text: "", parameters: {})
|
16
|
+
storage_adapter.save(
|
17
|
+
id: id,
|
18
|
+
text: text,
|
19
|
+
parameters: parameters
|
20
|
+
)
|
21
|
+
|
22
|
+
new(id: id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def search(for_what)
|
26
|
+
storage_adapter.search(for_what)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :db, :id, :text, :parameters
|
31
|
+
|
32
|
+
# FIXME: Assumes that the prompt ID exists in storage,
|
33
|
+
# wo how do we create a new one?
|
34
|
+
def initialize(
|
35
|
+
id: nil, # A String name for the prompt
|
36
|
+
context: [] # FIXME: Array of Strings or Pathname?
|
37
|
+
)
|
38
|
+
|
39
|
+
raise ArgumentError, 'id cannot be blank' if id.nil? || id.strip.empty?
|
40
|
+
|
41
|
+
@id = id
|
42
|
+
@db = self.class.storage_adapter
|
43
|
+
|
44
|
+
raise(ArgumentError, 'storage_adapter is not set') if db.nil?
|
45
|
+
|
46
|
+
@record = db.get(id: id)
|
47
|
+
@text = @record[:text]
|
48
|
+
@parameters = @record[:parameters]
|
49
|
+
|
50
|
+
@prompt = interpolate_parameters
|
51
|
+
end
|
52
|
+
|
53
|
+
# Displays the prompt text after parameter interpolation.
|
54
|
+
def to_s
|
55
|
+
@prompt
|
56
|
+
end
|
57
|
+
|
58
|
+
def save
|
59
|
+
db.save(
|
60
|
+
id: id,
|
61
|
+
text: text,
|
62
|
+
parameters: parameters
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def delete
|
68
|
+
db.delete(id: id)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
######################################
|
73
|
+
private
|
74
|
+
|
75
|
+
# Converts keys in the hash to lowercase symbols for easy parameter replacement.
|
76
|
+
def symbolize_and_downcase_keys(hash)
|
77
|
+
hash.map { |key, value| [key.to_s.downcase.to_sym, value] }.to_h
|
78
|
+
end
|
79
|
+
|
80
|
+
# Interpolate the parameters within the prompt.
|
81
|
+
def interpolate_parameters
|
82
|
+
text.gsub(PARAMETER_REGEX) do |match|
|
83
|
+
param_name = match[1..-2].downcase
|
84
|
+
parameters[param_name] || match
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# TODO: Implement and integrate ignore_after_end and apply the logic within initialize.
|
90
|
+
|
91
|
+
# TODO: Implement and integrate extract_raw_prompt and apply the logic within initialize.
|
92
|
+
|
93
|
+
# TODO: Implement a better error handling strategy for the storage methods (save, search, get).
|
94
|
+
|
95
|
+
# TODO: Refactor class to support more explicit and semantic configuration and setup.
|
96
|
+
|
97
|
+
# TODO: Consider adding a method to refresh the parameters and re-interpolate the prompt text.
|
98
|
+
|
99
|
+
# TODO: Check the responsibility of the save method; should it deal with the parameters directly or leave it to storage?
|
100
|
+
|
101
|
+
# TODO: Check overall consistency and readability of the code.
|
102
|
+
end
|
103
|
+
|
104
|
+
# Usage of the fixed class would change as follows:
|
105
|
+
# Assuming Storage is a defined class that manages storing and retrieving prompts.
|
106
|
+
# storage_instance = Storage.new(...)
|
107
|
+
# PromptManager::Prompt.storage_adapter = storage_instance
|
108
|
+
|
109
|
+
# prompt = PromptManager::Prompt.new(id: 'my_prompt_id')
|
110
|
+
# puts prompt.to_s
|
111
|
+
# Expected output would depend on the parameters stored with 'my_prompt_id'
|
112
|
+
|
113
|
+
__END__
|
114
|
+
|
115
|
+
def extract_raw_prompt
|
116
|
+
array_of_strings = ignore_after_end
|
117
|
+
print_header_comment(array_of_strings)
|
118
|
+
|
119
|
+
array_of_strings.reject do |a_line|
|
120
|
+
a_line.chomp.strip.start_with?('#')
|
121
|
+
end
|
122
|
+
.join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
def ignore_after_end
|
128
|
+
array_of_strings = configatron.prompt_path.readlines
|
129
|
+
.map{|a_line| a_line.chomp.strip}
|
130
|
+
|
131
|
+
x = array_of_strings.index("__END__")
|
132
|
+
|
133
|
+
unless x.nil?
|
134
|
+
array_of_strings = array_of_strings[..x-1]
|
135
|
+
end
|
136
|
+
|
137
|
+
array_of_strings
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
# Usage example:
|
146
|
+
prompt_text = "Hello, [NAME]! You are logged in as [ROLE]."
|
147
|
+
parameter_hash = { 'NAME' => 'Alice', 'ROLE' => 'Admin' }
|
148
|
+
file_paths = ['path/to/first_context', 'path/to/second_context']
|
149
|
+
|
150
|
+
prompt = Prompt.new(prompt_text, parameter_hash, *file_paths)
|
151
|
+
puts prompt.show
|
152
|
+
# Expected output: Hello, Alice! You are logged in as Admin.
|
153
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# prompt_manager/lib/prompt_manager/storage/active_record_adapter.rb
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
# TODO: Will need a database.yml file
|
6
|
+
# will need to know the column names that coorespond
|
7
|
+
# with the things that the Prompt class wants.
|
8
|
+
|
9
|
+
class PromptManager::Storage::ActiveRecordAdapter
|
10
|
+
attr_reader :model_class
|
11
|
+
|
12
|
+
def initialize(model_class)
|
13
|
+
@model_class = model_class
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def prompt_text(prompt_id)
|
18
|
+
prompt = find_prompt(prompt_id)
|
19
|
+
prompt.text
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def parameter_values(prompt_id)
|
24
|
+
prompt = find_prompt(prompt_id)
|
25
|
+
JSON.parse(prompt.params, symbolize_names: true)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def save(prompt_id, prompt_text, parameter_values)
|
30
|
+
prompt = model_class.find_or_initialize_by(id: prompt_id)
|
31
|
+
prompt.text = prompt_text
|
32
|
+
prompt.params = parameter_values.to_json
|
33
|
+
prompt.save!
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def delete(prompt_id)
|
38
|
+
prompt = find_prompt(prompt_id)
|
39
|
+
prompt.destroy
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def search(for_what)
|
44
|
+
# TODO: search through all prompts. Return an Array of
|
45
|
+
# prompt_id where the text of the prompt contains
|
46
|
+
# for_what is being searched.
|
47
|
+
|
48
|
+
[]
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
class << self
|
53
|
+
def config
|
54
|
+
# TODO: establish a connection to the database
|
55
|
+
# maybe define the prompts table and its
|
56
|
+
# columns of interest.
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
###############################################
|
61
|
+
private
|
62
|
+
|
63
|
+
def find_prompt(prompt_id)
|
64
|
+
model_class.find_by(id: prompt_id) || raise('Prompt not found')
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# prompt_manager/lib/prompt_manager/storage/file_system_adapter.rb
|
2
|
+
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class PromptManager::Storage::FileSystemAdapter
|
7
|
+
PARAMS_EXTENSION = '.json'.freeze
|
8
|
+
PROMPT_EXTENSION = '.txt'.freeze
|
9
|
+
|
10
|
+
attr_reader :prompts_dir
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
prompts_dir: '.prompts',
|
14
|
+
search_proc: nil # Example: ->(q) {`ag -l #{q}`}
|
15
|
+
)
|
16
|
+
|
17
|
+
# validate that prompts_dir exist and is in fact a directory.
|
18
|
+
unless Dir.exist?(prompts_dir)
|
19
|
+
raise "Directory #{prompts_dir} does not exist or is not a directory"
|
20
|
+
end
|
21
|
+
|
22
|
+
@prompts_dir = prompts_dir
|
23
|
+
@search_proc = search_proc
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def get(id:)
|
28
|
+
validate_id(id)
|
29
|
+
|
30
|
+
{
|
31
|
+
id: id,
|
32
|
+
text: prompt_text(id),
|
33
|
+
parameters: parameter_values(id)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Retrieve prompt text by its id
|
39
|
+
def prompt_text(prompt_id)
|
40
|
+
read_file(file_path(prompt_id, PROMPT_EXTENSION))
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Retrieve parameter values by its id
|
45
|
+
def parameter_values(prompt_id)
|
46
|
+
json_content = read_file(file_path(prompt_id, PARAMS_EXTENSION))
|
47
|
+
deserialize(json_content)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Save prompt text and parameter values to corresponding files
|
52
|
+
def save(
|
53
|
+
id:,
|
54
|
+
text: "",
|
55
|
+
parameters: {}
|
56
|
+
)
|
57
|
+
validate_id(id)
|
58
|
+
|
59
|
+
prompt_filepath = file_path(id, PROMPT_EXTENSION)
|
60
|
+
params_filepath = file_path(id, PARAMS_EXTENSION)
|
61
|
+
|
62
|
+
write_with_error_handling(prompt_filepath, text)
|
63
|
+
write_with_error_handling(params_filepath, serialize(parameters))
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Delete prompted text and parameter values files
|
68
|
+
def delete(id:)
|
69
|
+
validate_id(id)
|
70
|
+
|
71
|
+
prompt_filepath = file_path(id, PROMPT_EXTENSION)
|
72
|
+
params_filepath = file_path(id, PARAMS_EXTENSION)
|
73
|
+
|
74
|
+
delete_with_error_handling(prompt_filepath)
|
75
|
+
delete_with_error_handling(params_filepath)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def search(for_what)
|
80
|
+
if @search_proc
|
81
|
+
@search_proc.call(for_what)
|
82
|
+
else
|
83
|
+
search_prompts(for_what)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
##########################################
|
89
|
+
private
|
90
|
+
|
91
|
+
def validate_id(id)
|
92
|
+
raise ArgumentError, 'Invalid ID format' unless id =~ /^[a-zA-Z0-9\-_]+$/
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def write_with_error_handling(file_path, content)
|
97
|
+
begin
|
98
|
+
File.write(file_path, content)
|
99
|
+
rescue IOError => e
|
100
|
+
raise "Failed to write to file: #{e.message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def delete_with_error_handling(file_path)
|
106
|
+
begin
|
107
|
+
FileUtils.rm_f(file_path)
|
108
|
+
rescue IOError => e
|
109
|
+
raise "Failed to delete file: #{e.message}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def file_path(id, extension)
|
115
|
+
File.join(@prompts_dir, "#{id}#{extension}")
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def read_file(full_path)
|
120
|
+
raise IOError, 'File does not exist' unless File.exist?(full_path)
|
121
|
+
File.read(full_path)
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def search_prompts(search_term)
|
126
|
+
query_term = search_term.downcase
|
127
|
+
|
128
|
+
Dir.glob(File.join(@prompts_dir, "*#{PROMPT_EXTENSION}")).each_with_object([]) do |file_path, ids|
|
129
|
+
File.open(file_path) do |file|
|
130
|
+
file.each_line do |line|
|
131
|
+
if line.downcase.include?(query_term)
|
132
|
+
ids << File.basename(file_path, PROMPT_EXTENSION)
|
133
|
+
next
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end.uniq
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def serialize(data)
|
142
|
+
data.to_json
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def deserialize(data)
|
147
|
+
JSON.parse(data, symbolize_names: true)
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# prompt_manager/lib/prompt_manager/storage/sqlite_adapter.rb
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'sqlite3'
|
5
|
+
|
6
|
+
class PromptManager::Storage::SqliteAdapter
|
7
|
+
def initialize(db_path = 'prompts.db')
|
8
|
+
@db = SQLite3::Database.new(db_path)
|
9
|
+
create_tables
|
10
|
+
end
|
11
|
+
|
12
|
+
def save_prompt(prompt_id, text, params)
|
13
|
+
@db.execute("INSERT INTO prompts (id, text, params) VALUES (?, ?, ?)",
|
14
|
+
[prompt_id, text, params.to_json])
|
15
|
+
end
|
16
|
+
|
17
|
+
def prompt_text(prompt_id)
|
18
|
+
result = @db.get_first_value("SELECT text FROM prompts WHERE id = ?", [prompt_id])
|
19
|
+
raise 'Prompt not found' if result.nil?
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def parameter_values(prompt_id)
|
24
|
+
json_content = @db.get_first_value("SELECT params FROM prompts WHERE id = ?", [prompt_id])
|
25
|
+
raise 'Parameters not found' if json_content.nil?
|
26
|
+
JSON.parse(json_content, symbolize_names: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def save(prompt_id, prompt_text, parameter_values)
|
30
|
+
@db.execute(
|
31
|
+
'REPLACE INTO prompts (id, text, params) VALUES (?, ?, ?)',
|
32
|
+
[prompt_id, prompt_text, parameter_values.to_json]
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(prompt_id)
|
37
|
+
@db.execute('DELETE FROM prompts WHERE id = ?', [prompt_id])
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def search(for_what)
|
42
|
+
# TODO: search through all prompts. Return an Array of
|
43
|
+
# prompt_id where the text of the prompt contains
|
44
|
+
# for_what is being searched.
|
45
|
+
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
###################################################
|
50
|
+
private
|
51
|
+
|
52
|
+
def create_tables
|
53
|
+
@db.execute <<-SQL
|
54
|
+
CREATE TABLE IF NOT EXISTS prompts (
|
55
|
+
id TEXT PRIMARY KEY,
|
56
|
+
text TEXT NOT NULL,
|
57
|
+
params TEXT NOT NULL
|
58
|
+
);
|
59
|
+
SQL
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
data/lib/prompt_manager.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
+
# prompt_manager/lib/prompt_manager.rb
|
2
|
+
#
|
1
3
|
# frozen_string_literal: true
|
2
4
|
|
5
|
+
require 'debug_me'
|
6
|
+
include DebugMe
|
7
|
+
|
3
8
|
require_relative "prompt_manager/version"
|
9
|
+
require_relative "prompt_manager/storage"
|
10
|
+
require_relative "prompt_manager/prompt"
|
4
11
|
|
5
12
|
module PromptManager
|
6
13
|
class Error < StandardError; end
|
7
|
-
# Your code goes here...
|
8
14
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prompt_manager
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dewayne VanHoozer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Manage parameterized prompt text for use with GPT/LLM
|
14
14
|
email:
|
@@ -23,10 +23,16 @@ files:
|
|
23
23
|
- LICENSE.txt
|
24
24
|
- README.md
|
25
25
|
- Rakefile
|
26
|
-
-
|
26
|
+
- examples/aip.rb
|
27
|
+
- examples/prompts_dir/todo.json
|
28
|
+
- examples/prompts_dir/todo.txt
|
27
29
|
- lib/prompt_manager.rb
|
30
|
+
- lib/prompt_manager/prompt.rb
|
31
|
+
- lib/prompt_manager/storage.rb
|
32
|
+
- lib/prompt_manager/storage/active_record_adapter.rb
|
33
|
+
- lib/prompt_manager/storage/file_system_adapter.rb
|
34
|
+
- lib/prompt_manager/storage/sqlite_adapter.rb
|
28
35
|
- lib/prompt_manager/version.rb
|
29
|
-
- lib/temp.md
|
30
36
|
homepage: https://github.com/MadBomber/prompt_manager
|
31
37
|
licenses:
|
32
38
|
- MIT
|
@@ -50,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
56
|
- !ruby/object:Gem::Version
|
51
57
|
version: '0'
|
52
58
|
requirements: []
|
53
|
-
rubygems_version: 3.
|
59
|
+
rubygems_version: 3.4.22
|
54
60
|
signing_key:
|
55
61
|
specification_version: 4
|
56
62
|
summary: Manage prompts for use with chatGPT LLMs
|
data/lib/temp.md
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
### README for aip.rb Ruby Script
|
2
|
-
|
3
|
-
#### Overview
|
4
|
-
|
5
|
-
The `aip.rb` Ruby script is a command-line interface (CLI) tool designed to leverage generative AI with saved parameterized prompts. It integrates with the `mods` command-line tool that uses a GPT-based model to generate responses based on user-provided prompts. The script offers an array of features that make interacting with AI models more convenient and streamlined.
|
6
|
-
|
7
|
-
#### Features
|
8
|
-
|
9
|
-
- **Prompt Management**
|
10
|
-
- Users can select prompts from a saved collection with the help of command-line searching and filtering.
|
11
|
-
- Prompts can be edited by the user to better fit their specific context or requirement.
|
12
|
-
- Support for reading input from files to provide context for AI generation is included.
|
13
|
-
|
14
|
-
- **AI Integration**
|
15
|
-
- The script interacts with `mods`, a generative AI utilizing GPT-based models, to produce outputs from the prompts.
|
16
|
-
|
17
|
-
- **Output Handling**
|
18
|
-
- Generated content is saved to a specified file for record-keeping and further use.
|
19
|
-
|
20
|
-
- **Activity Logging**
|
21
|
-
- All actions, including prompt usage and AI output, are logged with timestamps for review and auditing purposes.
|
22
|
-
|
23
|
-
#### Dependencies
|
24
|
-
|
25
|
-
The script requires the installation of the following command-line tools:
|
26
|
-
|
27
|
-
- `fzf`: a powerful command-line fuzzy finder.
|
28
|
-
- `mods`: an AI-powered CLI tool for generative AI interactions.
|
29
|
-
- `the_silver_searcher (ag)`: a code-searching tool similar to ack and used for searching prompts.
|
30
|
-
|
31
|
-
#### Usage
|
32
|
-
|
33
|
-
The `aip.rb` script offers a set of command-line options to guide the interaction with AI:
|
34
|
-
|
35
|
-
- `-p, --prompt`: Specify the prompt name to be used.
|
36
|
-
- `-e, --edit`: Open the prompt text for editing before generation.
|
37
|
-
- `-f, --fuzzy`: Allows fuzzy matching for prompt selection.
|
38
|
-
- `-o, --output`: Sets the output file for the generated content.
|
39
|
-
|
40
|
-
Additional flags and options can be passed to the `mods` tool by appending them after a `--` separator.
|
41
|
-
|
42
|
-
#### Installation
|
43
|
-
|
44
|
-
Before using the script, one must ensure the required command-line tools (`fzf`, `mods`, and `the_silver_searcher`) are installed, and the Ruby environment is correctly set up with the necessary gems.
|
45
|
-
|
46
|
-
#### Development Notes
|
47
|
-
|
48
|
-
The author suggests that the script has matured enough to be converted into a Ruby gem for easier distribution and installation.
|
49
|
-
|
50
|
-
#### Getting Help
|
51
|
-
|
52
|
-
For help with using the CLI tool or further understanding the `mods` command, users can refer to the AI CLI Program help section included in the script or by invoking the `--help` flag.
|
53
|
-
|
54
|
-
#### Conclusion
|
55
|
-
|
56
|
-
The `aip.rb` script is designed to offer a user-friendly and flexible approach to integrating generative AI into content creation processes. It streamlines the interactions and management of AI-generated content by providing prompt management, AI integration, and logging capabilities, packaged inside a simple command-line utility.
|
57
|
-
|