prompt_manager 0.2.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c96927dad3be1d7e793e3edd5d3df3e3a12999fcec57ad01d3d773d9b40e674
4
- data.tar.gz: 4aa47ce704540fa933dbdf30d3afddbb3704bd541b4dab1f7a751e0e461f3c1e
3
+ metadata.gz: add8f0261dd0fd2e91e8195330d923e8806c2e3a7243cfb51ce70a589fc76ef2
4
+ data.tar.gz: 73b69061afdd242679c231949fba6a3b075f486feb20113cb838d0345dd2ffcf
5
5
  SHA512:
6
- metadata.gz: '0857d6d0532c0c1293799e760933e5bf2ee1a82f8cfdce6dd649b187f724e31935bf959b04c8058a90877525751cf99deaa4fd51a9cc64d0259a3d4b1e40de38'
7
- data.tar.gz: 59d2e1cabed3a87766092f56eae6ed7e7d30a2c0d0b3e3dc2efd93fe9ce79d97ce7ee5af3d1beae6896aed3a1754649dc7a7f3bbdec1e0566015640d8f7f2128
6
+ metadata.gz: a65630d9623b49c34265b137f03cfb032ec645f736e9ca9327e2aaf045cdd0e2818e28e1b000bc80b32016f32a59192861db6cc7596c8ea6834b2c386ce1aac2
7
+ data.tar.gz: ec16e90d781c354875932d454d04783bcd46b10f7a815ffe94204fd17475bc2930cb6627e55072ec9b937512031033bdf01eda83d62ed126d2833801fd156e0c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] = 2023-11-28
4
+
5
+ - **Breaking change** The value of the parameters Hash for a keyword is now an Array instead of a single value. The last value in the Array is always the most recent value used for the given keyword. This was done to support the use of a Readline::History object editing in the [aia](https://github.com/MadBomber/aia) CLI tool
6
+
3
7
  ## [0.2.0] - 2023-11-21
4
8
 
5
9
  - **Breaking change to FileSystemAdapter config process**
data/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  Manage the parameterized prompts (text) used in generative AI (aka chatGPT, OpenAI, _et.al._) using storage adapters such as FileSystemAdapter, SqliteAdapter and ActiveRecordAdapter.
4
4
 
5
- **Breaking Change** in version 0.2.0 for `FileSystemAdapter` configuration. See [Configuration](#configuration) to see how the new `config` block works.
5
+ **Breaking Change** in version 0.3.0 - The value of the parameters Hash for a keyword is now an Array instead of a single value. The last value in the Array is always the most recent value used for the given keyword. This was done to support the use of a Readline::History object editing in the [aia](https://github.com/MadBomber/aia) CLI tool
6
+
6
7
 
7
8
  <!-- Tocer[start]: Auto-generated, don't remove. -->
8
9
 
@@ -19,6 +20,8 @@ Manage the parameterized prompts (text) used in generative AI (aka chatGPT, Open
19
20
  - [prompts_dir](#prompts_dir)
20
21
  - [search_proc](#search_proc)
21
22
  - [File Extensions](#file-extensions)
23
+ - [Example Prompt Text File](#example-prompt-text-file)
24
+ - [Example Prompt Parameters JSON File](#example-prompt-parameters-json-file)
22
25
  - [Extra Functionality](#extra-functionality)
23
26
  - [SqliteAdapter](#sqliteadapter)
24
27
  - [ActiveRecordAdapter](#activerecordadapter)
@@ -118,6 +121,36 @@ Currently the `FileSystemAdapter` only supports a JSON serializer for its parame
118
121
 
119
122
  They exist so that there is a platform on to which other storage adapters can be built or serializers added. This is not currently on the roadmap.
120
123
 
124
+ ##### Example Prompt Text File
125
+
126
+ ```text
127
+ # ~/.prompts/joke.txt
128
+ # Desc: Tell some jokes
129
+
130
+ Tell me a few [KIND] jokes about [SUBJECT]
131
+ ```
132
+
133
+ Note the command lines at the top. This is a convention I use. It is not part of the software. I find it helpful in documenting the prompt.
134
+
135
+ ##### Example Prompt Parameters JSON File
136
+
137
+ ```json
138
+ {
139
+ "[KIND]": [
140
+ "pun",
141
+ "family friendly"
142
+ ],
143
+ "[SUBJECT]": [
144
+ "parrot",
145
+ "garbage man",
146
+ "snowman",
147
+ "weather girl"
148
+ ]
149
+ }
150
+ ```
151
+
152
+ The last value in the keyword's Array is the most recent value used for that keyword. This is a functionality established since v0.3.0. Its purpose is to provide a history of values from which a user can select to repeat a previous value or to select ta previous value and edit it into something new.
153
+
121
154
  ##### Extra Functionality
122
155
 
123
156
  The `FileSystemAdapter` adds two new methods for use by the `Prompt` class:
@@ -81,8 +81,9 @@ class PromptManager::Prompt
81
81
  # Return tje prompt text suitable for passing to a
82
82
  # gen-AI process.
83
83
  def to_s
84
- @prompt
84
+ build
85
85
  end
86
+ alias_method :prompt, :to_s
86
87
 
87
88
 
88
89
  # Save the prompt to the Storage system
@@ -108,20 +109,27 @@ class PromptManager::Prompt
108
109
  def build
109
110
  @prompt = text.gsub(PARAMETER_REGEX) do |match|
110
111
  param_name = match
111
- parameters[param_name] || match
112
+ Array(parameters[param_name]).last || match
112
113
  end
113
114
 
114
115
  remove_comments
115
116
  end
116
117
 
118
+
119
+ def keywords
120
+ update_keywords
121
+ end
122
+
117
123
  ######################################
118
124
  private
119
125
 
120
126
  def update_keywords
121
127
  @keywords = @text.scan(PARAMETER_REGEX).flatten.uniq
122
- keywords.each do |kw|
123
- @parameters[kw] = "" unless @parameters.has_key?(kw)
128
+ @keywords.each do |kw|
129
+ @parameters[kw] = [] unless @parameters.has_key?(kw)
124
130
  end
131
+
132
+ @keywords
125
133
  end
126
134
 
127
135
 
@@ -63,4 +63,99 @@ class PromptManager::Storage::ActiveRecordAdapter
63
63
  def find_prompt(prompt_id)
64
64
  model_class.find_by(id: prompt_id) || raise('Prompt not found')
65
65
  end
66
- end
66
+ end
67
+
68
+
69
+ __END__
70
+
71
+ # prompt_manager/lib/prompt_manager/storage/active_record_adapter.rb
72
+
73
+ require 'active_record'
74
+
75
+ module PromptManager
76
+ module Storage
77
+ class ActiveRecordAdapter
78
+
79
+ # Define models for ActiveRecord
80
+ class Prompt < ActiveRecord::Base
81
+ validates :unique_id, presence: true
82
+ validates :text, presence: true
83
+ end
84
+
85
+ class PromptParameter < ActiveRecord::Base
86
+ belongs_to :prompt
87
+ validates :key, presence: true
88
+ serialize :value
89
+ end
90
+
91
+ def initialize
92
+ unless ActiveRecord::Base.connected?
93
+ raise ArgumentError, "ActiveRecord is not connected"
94
+ end
95
+ end
96
+
97
+ def get(id:)
98
+ prompt = Prompt.find_by(unique_id: id)
99
+ return nil unless prompt
100
+
101
+ parameters = prompt.prompt_parameters.index_by(&:key)
102
+
103
+ {
104
+ id: prompt.unique_id,
105
+ text: prompt.text,
106
+ parameters: parameters.transform_values(&:value)
107
+ }
108
+ end
109
+
110
+ def save(id:, text: "", parameters: {})
111
+ prompt = Prompt.find_or_initialize_by(unique_id: id)
112
+ prompt.text = text
113
+ prompt.save!
114
+
115
+ parameters.each do |key, value|
116
+ parameter = PromptParameter.find_or_initialize_by(prompt: prompt, key: key)
117
+ parameter.value = value
118
+ parameter.save!
119
+ end
120
+ end
121
+
122
+ def delete(id:)
123
+ prompt = Prompt.find_by(unique_id: id)
124
+ return unless prompt
125
+
126
+ prompt.prompt_parameters.destroy_all
127
+ prompt.destroy
128
+ end
129
+
130
+ def search(for_what)
131
+ query = '%' + for_what.downcase + '%'
132
+ Prompt.where('LOWER(text) LIKE ?', query).pluck(:unique_id)
133
+ end
134
+
135
+ def list(*)
136
+ Prompt.pluck(:unique_id)
137
+ end
138
+
139
+ private
140
+
141
+ # This is an example of how the database connection setup could look like,
142
+ # but it should be handled externally in the actual application setup.
143
+ def self.setup_database_connection
144
+ ActiveRecord::Base.establish_connection(
145
+ adapter: 'sqlite3',
146
+ database: 'prompts.db'
147
+ )
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ # After this, you would need to create a database migration to generate the required tables.
154
+ # Additionally, you have to establish an ActiveRecord connection before using this adapter,
155
+ # typically in the environment setup of your application.
156
+
157
+ # Keep in mind you need to create migrations for both the Prompt and PromptParameter models,
158
+ # and manage the database schema using ActiveRecord migrations. This adapter assumes that the
159
+ # database structure is already in place and follows the schema inferred by the models in the adapter.
160
+
161
+
@@ -258,6 +258,7 @@ class PromptManager::Storage::FileSystemAdapter
258
258
  def write_with_error_handling(file_path, content)
259
259
  begin
260
260
  file_path.write content
261
+ true
261
262
  rescue IOError => e
262
263
  raise "Failed to write to file: #{e.message}"
263
264
  end
@@ -268,6 +269,7 @@ class PromptManager::Storage::FileSystemAdapter
268
269
  def delete_with_error_handling(file_path)
269
270
  begin
270
271
  file_path.delete
272
+ true
271
273
  rescue IOError => e
272
274
  raise "Failed to delete file: #{e.message}"
273
275
  end
@@ -61,5 +61,96 @@ class PromptManager::Storage::SqliteAdapter
61
61
  end
62
62
 
63
63
 
64
+ __END__
65
+
66
+
67
+ require 'sequel'
68
+ require 'pathname'
69
+ require 'json'
70
+
71
+ module PromptManager
72
+ module Storage
73
+ class SqliteAdapter
74
+ DEFAULT_DB_FILE = 'prompts.sqlite3'.freeze
75
+
76
+ class << self
77
+ attr_accessor :db_file, :db
78
+
79
+ def config
80
+ if block_given?
81
+ yield self
82
+ else
83
+ raise ArgumentError, 'No block given to config'
84
+ end
85
+
86
+ self.db = Sequel.sqlite(db_file || DEFAULT_DB_FILE) # Use provided db_file or default
87
+ create_tables unless db.table_exists?(:prompts)
88
+ end
89
+
90
+ # Define the necessary tables within the SQLite database if they don't exist
91
+ def create_tables
92
+ db.create_table :prompts do
93
+ primary_key :id
94
+ String :prompt_id, unique: true, null: false
95
+ Text :text
96
+ Json :parameters
97
+
98
+ index :prompt_id
99
+ end
100
+ end
101
+ end
102
+
103
+ def initialize
104
+ @db = self.class.db
105
+ end
106
+
107
+ def get(id:)
108
+ validate_id(id)
109
+ result = @db[:prompts].where(prompt_id: id).first
110
+ raise ArgumentError, 'Prompt not found' unless result
111
+
112
+ {
113
+ id: result[:prompt_id],
114
+ text: result[:text],
115
+ parameters: result[:parameters]
116
+ }
117
+ end
118
+
119
+ def save(id:, text: '', parameters: {})
120
+ validate_id(id)
121
+ rec = @db[:prompts].where(prompt_id: id).first
122
+ if rec
123
+ @db[:prompts].where(prompt_id: id).update(text: text, parameters: Sequel.pg_json(parameters))
124
+ else
125
+ @db[:prompts].insert(prompt_id: id, text: text, parameters: Sequel.pg_json(parameters))
126
+ end
127
+ end
128
+
129
+ def delete(id:)
130
+ validate_id(id)
131
+ @db[:prompts].where(prompt_id: id).delete
132
+ end
133
+
134
+ # Return an Array of prompt IDs
135
+ def list()
136
+ @db[:prompts].select_map(:prompt_id)
137
+ end
138
+
139
+ def search(for_what)
140
+ search_term = for_what.downcase
141
+ @db[:prompts].where(Sequel.ilike(:text, "%#{search_term}%")).select_map(:prompt_id)
142
+ end
143
+
144
+ private
145
+
146
+ # Validate that the ID contains good characters.
147
+ def validate_id(id)
148
+ raise ArgumentError, "Invalid ID format id: #{id}" unless id =~ /^[a-zA-Z0-9\-\/_]+$/
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+
64
155
 
65
156
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PromptManager
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  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.2.1
4
+ version: 0.3.1
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-23 00:00:00.000000000 Z
11
+ date: 2023-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amazing_print