action_prompt 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d077c592d1944448a6fee2d5eb1184c3e8a3c42cccb21929fdae60fa9806aed2
4
- data.tar.gz: 347712c9bc12ce13c43fe3a983c29a90776426e1f2d88a2ef7ea8477cb1aae3a
3
+ metadata.gz: de8ad6099ba3b0e8ff896d96e8ff8a543a9558d09c129d248b778bbf33ca5b36
4
+ data.tar.gz: b4473d1847ebf29e43c57461f8397281b6ccdb12132673517477a7a424c210da
5
5
  SHA512:
6
- metadata.gz: 17ffba4a0d8ec79e77b25c1337bacfbe83523976cfa69f3c26d4272da9d859e1d4821575b6695073916b5bac71cd7ba990924dbdcc37acb683363822f488fe55
7
- data.tar.gz: ee62f030e9df50de5b0a5783bf103d036a028e6cdaab5c829a97021892595b2bbfd2ecc795e1f8f9a04032370cce31dcc4f11df2cab5d3e872f1058d61f7ac81
6
+ metadata.gz: 56349f3e9365bee80336405ae7a04a3d8cee84fe83571c011f4f4fda49bc8048ac257853bd7d205fdbef72edad1275e4cd2960278a6bebd4534c3ffc9643b3d5
7
+ data.tar.gz: 385a20ef932cdf6d45e261b241333c886c1ab39e6feb6b10c265c207f05fe2e7ad350bd30e9c68bd7235d8eb7264ef26288d3cf2e8c501c7ef687b65f04f232d
data/README.md CHANGED
@@ -1,28 +1,61 @@
1
- # ActionPrompt
2
- Short description and motivation.
1
+ # Action Prompt
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ Action Prompt provides a dead simple way to way to organize, preview, and render prompts within a Ruby on Rails App. Because this leverages the `ApplicationController`, you're able to leverage all the bells an whistles
4
+
5
+ This draws heavy inspiration from `ActionMailer::Preview`.
6
+
7
+ > [!IMPORTANT]
8
+ > This gem is a work-in-progress. **It is not production ready** . That said, `ActionPrompt` is under active development. Any and all feedback would be very welcome. Or hey, feel free to open a PR.
9
+
10
+ ## Motivation & Usage
11
+
12
+ As LLMs have become ubiquitous in web applications, we've that prompts intended for Claude or GPT have become scattered throughout our codebase or buried within objects. Often, these prompts were built inline through string manipulation. Our thinking was two-fold, 1) Let's come up with a simple pattern for organizing and rendering these prompts, and 2) Let's make them easy to review.
6
13
 
7
14
  ## Installation
8
- Add this line to your application's Gemfile:
9
15
 
10
- ```ruby
11
- gem "action_prompt"
12
- ```
16
+ Install the gem with `gem "action_prompt"`.
17
+
18
+ ## Organizing & Previewing Prompts
19
+
20
+ 1. Create a template for organizing your prompts located at your `app/prompts`. For example, you might create `app/prompts/hello_world.text.erb` and give it the following content:
21
+
22
+ ```erb
23
+ You are a helpful assistant who replies with, "<%= @message >"
24
+ ```
25
+
26
+ 2. Create a preview class. These live in `test/prompts` and they inherit from `ActionPrompt::Preview`. For example, you might create `tests/prompts/hello_world_preview.rb` and give it the following context:
13
27
 
14
- And then execute:
15
- ```bash
16
- $ bundle
17
- ```
28
+ ```ruby
29
+ class HelloWorldPreview < ActionPrompt::Preview
30
+ def example_prompt
31
+ render "hello_world", locals: {message: "Hello, world!"}
32
+ end
33
+ end
34
+ ```
18
35
 
19
- Or install it yourself as:
20
- ```bash
21
- $ gem install action_prompt
22
- ```
36
+ 3. Next, start up your rails server (`rails s`) and navigate to [http://localhost:3000/action_prompt/previews](http://localhost:3000/action_prompt/previews). You'll see a list of your prompts that resembles the following:
23
37
 
24
- ## Contributing
25
- Contribution directions go here.
38
+ ![Screenshot](docs/assets/images/screenshot.png)
39
+
40
+ You can now preview your prompts.
41
+
42
+ ## Rendering prompts within your app
43
+
44
+ 1. Assume you've followed step one above, and you have a prompt located at `app/prompts/hello_world.text.erb`.
45
+ 2. You can now render this anywhere in your codebase with the following:
46
+
47
+ ```ruby
48
+ ActionPrompt::Renderer.new.render("hello_world", locals: {message: "Now we're cooking"})
49
+ # You are a helpful assistant who replies with, "Now we're cooking"
50
+ ```
51
+
52
+ Under the hood, `ActionPrompt` is leveraging the app's `ApplicationController`. That means you can use the full magic of `ActionView` which includes
53
+
54
+ - Rendering partials.
55
+ - Rendering json with `Jbuilder`
56
+ - Using route helpers, i.e. `posts_url`
57
+ - ...and more!
26
58
 
27
59
  ## License
60
+
28
61
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,22 @@
1
+ class ActionPrompt::PreviewsController < ActionController::Base
2
+ prepend_view_path ActionPrompt::Engine.root.join("app", "views")
3
+ layout "application"
4
+ # Ideally, we'd like to inhert from Rails::ApplicationController, but that
5
+ # would prevent us from using a Tailwind CDN. So instead, we're using ActionController::Base.
6
+ #
7
+ # If we are able to use Rails::ApplicationController, then re-enable this line
8
+ # before_action :require_local!
9
+
10
+ def index
11
+ @page_title = "Action Prompt Previews"
12
+ @previews = ActionPrompt::Preview.all
13
+ end
14
+
15
+ def show
16
+ preview_class_name = params[:preview_slug].camelize + "Preview"
17
+ @preview_class = ActionPrompt::Preview.find(preview_class_name)
18
+ slug = "#{params[:preview_slug]}/#{params[:prompt_name]}"
19
+ @prompt = @preview_class.find_prompt(slug)
20
+ @prompt_output = @preview_class.new.send(params[:prompt_name].to_sym)
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ <h1 class="text-xl font-semibold">Action Prompt Previews</h1>
2
+ <div class="py-4">
3
+ <% if @previews.blank? %>
4
+ <p>You have not defined any Action Prompt Previews.</p>
5
+ <!-- TODO: Add a link to the Action Prompt documentation here -->
6
+ <% else %>
7
+ <div class="space-y-4">
8
+ <% @previews.each do |preview| %>
9
+ <div>
10
+ <h2 class="text-lg font-semibold"><%= preview.display_name %></h2>
11
+ <ul>
12
+ <% preview.prompts.each do |prompt| %>
13
+ <li><%= link_to prompt.name, "/action_prompt/previews/#{prompt.slug}", class: "cursor-pointer underline text-blue-600 hover:text-blue-800" %></li>
14
+ <% end %>
15
+ </ul>
16
+ </div>
17
+ <% end %>
18
+ </div>
19
+ <% end %>
20
+ </div>
@@ -0,0 +1,12 @@
1
+ <h1 class="text-xl font-semibold">
2
+ <%= @preview_class.display_name %>: <%= @prompt.name %>
3
+ </h1>
4
+ <div class="pb-2">
5
+ <%= link_to "<< Back to prompts", "/action_prompt/previews", class: "cursor-pointer underline text-blue-600 hover:text-blue-800 text-sm" %>
6
+ </div>
7
+ <div class="py-2">
8
+ <div>
9
+ Output
10
+ </div>
11
+ <div class="bg-gray-100 p-2 rounded-md border shadow-sm" style="word-wrap: break-word; white-space: pre-wrap;"><%= @prompt_output %></div>
12
+ </div>
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= @page_title %></title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ </head>
9
+ <body class="min-h-screen bg-gray-100 pt-4">
10
+
11
+ <div class="mx-auto w-2/3 bg-white rounded-lg p-8 shadow-lg border-2">
12
+ <%= yield %>
13
+ </div>
14
+
15
+ </body>
16
+ </html>
@@ -1,4 +1,5 @@
1
1
  module ActionPrompt
2
2
  class Engine < ::Rails::Engine
3
+ isolate_namespace ActionPrompt
3
4
  end
4
5
  end
@@ -0,0 +1,55 @@
1
+ require "active_support/descendants_tracker"
2
+
3
+ module ActionPrompt
4
+ class Preview
5
+ # We need to be able to do the following:
6
+ # - find all the preview objects
7
+ # - find all the preview methods on those objects
8
+ # - render a preview
9
+ #
10
+ class << self
11
+ def all
12
+ load_previews if descendants.empty?
13
+ descendants.sort_by(&:name)
14
+ end
15
+
16
+ def load_previews
17
+ # TODO: this preview path could be made configurable in order to have equivalent
18
+ # functionality to Rails mailer previews
19
+ Dir[Rails.root.join("test", "prompts", "**", "*_preview.rb")].each do |path|
20
+ require path
21
+ end
22
+ end
23
+
24
+ def display_name
25
+ name.titleize
26
+ end
27
+
28
+ def slug
29
+ name.delete_suffix("Preview").underscore
30
+ end
31
+
32
+ def prompts
33
+ # TODO: this might benefit from some memoization
34
+ prompt_methods = public_instance_methods(false).map(&:to_s).sort
35
+
36
+ prompt_methods.map do |method_name|
37
+ Prompt.new(name: method_name.humanize,
38
+ slug: "#{slug}/#{method_name}")
39
+ end
40
+ end
41
+
42
+ def find(name)
43
+ all.find { |preview| preview.name == name }
44
+ end
45
+
46
+ def find_prompt(slug)
47
+ prompts.find { |p| p.slug == slug }
48
+ end
49
+ end
50
+
51
+ def render(template_name, locals: {})
52
+ ActionPrompt::Renderer.new.render(template_name, locals: locals)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ module ActionPrompt
2
+ class Prompt
3
+ attr_reader :name, :slug
4
+
5
+ # NOTE: this could probably be a Struct
6
+ def initialize(name:, slug:)
7
+ @name = name
8
+ @slug = slug
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ require "rails/application_controller"
2
+
3
+ module ActionPrompt
4
+ class Railtie < Rails::Railtie
5
+ config.before_configuration do |app|
6
+ app.config.autoload_paths << Rails.root.join("tests/prompts")
7
+ end
8
+
9
+ config.after_initialize do |app|
10
+ if Rails.env.development? || Rails.env.test?
11
+ app.routes.prepend do
12
+ get "/action_prompt/previews", to: "action_prompt/previews#index" # , internal: true
13
+ get "/action_prompt/previews/:preview_slug/:prompt_name", to: "action_prompt/previews#show" # , internal: true
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module ActionPrompt
2
+ class Renderer
3
+ def initialize
4
+ # NOOP
5
+ end
6
+
7
+ def render(template_name, locals: {})
8
+ controller = ApplicationController.new
9
+ controller.prepend_view_path(Rails.root.join("app", "prompts"))
10
+ controller.render_to_string(template: template_name, locals: locals, layout: false)
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module ActionPrompt
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/action_prompt.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "action_prompt/version"
2
2
  require "action_prompt/engine"
3
-
3
+ require "action_prompt/railtie"
4
+ require "action_prompt/prompt"
5
+ require "action_prompt/preview"
6
+ require "action_prompt/renderer"
4
7
  module ActionPrompt
5
- # Your code goes here...
6
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_prompt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Arnold
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-28 00:00:00.000000000 Z
11
+ date: 2024-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -35,12 +35,18 @@ files:
35
35
  - README.md
36
36
  - Rakefile
37
37
  - app/assets/config/action_prompt_manifest.js
38
+ - app/controllers/action_prompt/previews_controller.rb
39
+ - app/views/action_prompt/previews/index.html.erb
40
+ - app/views/action_prompt/previews/show.html.erb
41
+ - app/views/layouts/application.html.erb
38
42
  - config/routes.rb
39
43
  - lib/action_prompt.rb
40
- - lib/action_prompt/base.rb
41
44
  - lib/action_prompt/engine.rb
45
+ - lib/action_prompt/preview.rb
46
+ - lib/action_prompt/prompt.rb
47
+ - lib/action_prompt/railtie.rb
48
+ - lib/action_prompt/renderer.rb
42
49
  - lib/action_prompt/version.rb
43
- - lib/tasks/action_prompt_tasks.rake
44
50
  homepage: https://github.com/evdevdev/action_prompt
45
51
  licenses:
46
52
  - MIT
@@ -61,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
67
  - !ruby/object:Gem::Version
62
68
  version: '0'
63
69
  requirements: []
64
- rubygems_version: 3.5.7
70
+ rubygems_version: 3.5.19
65
71
  signing_key:
66
72
  specification_version: 4
67
73
  summary: ActionPrompt is a Rails plugin for managing templated LLM prompts
@@ -1,6 +0,0 @@
1
- module ActionPrompt
2
- class Base
3
- def self.render_prompt(template_name, locals: {})
4
- end
5
- end
6
- end
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :action_prompt do
3
- # # Task goes here
4
- # end