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 +4 -4
- data/README.md +51 -18
- data/app/controllers/action_prompt/previews_controller.rb +22 -0
- data/app/views/action_prompt/previews/index.html.erb +20 -0
- data/app/views/action_prompt/previews/show.html.erb +12 -0
- data/app/views/layouts/application.html.erb +16 -0
- data/lib/action_prompt/engine.rb +1 -0
- data/lib/action_prompt/preview.rb +55 -0
- data/lib/action_prompt/prompt.rb +11 -0
- data/lib/action_prompt/railtie.rb +18 -0
- data/lib/action_prompt/renderer.rb +13 -0
- data/lib/action_prompt/version.rb +1 -1
- data/lib/action_prompt.rb +4 -2
- metadata +11 -5
- data/lib/action_prompt/base.rb +0 -6
- data/lib/tasks/action_prompt_tasks.rake +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de8ad6099ba3b0e8ff896d96e8ff8a543a9558d09c129d248b778bbf33ca5b36
|
4
|
+
data.tar.gz: b4473d1847ebf29e43c57461f8397281b6ccdb12132673517477a7a424c210da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56349f3e9365bee80336405ae7a04a3d8cee84fe83571c011f4f4fda49bc8048ac257853bd7d205fdbef72edad1275e4cd2960278a6bebd4534c3ffc9643b3d5
|
7
|
+
data.tar.gz: 385a20ef932cdf6d45e261b241333c886c1ab39e6feb6b10c265c207f05fe2e7ad350bd30e9c68bd7235d8eb7264ef26288d3cf2e8c501c7ef687b65f04f232d
|
data/README.md
CHANGED
@@ -1,28 +1,61 @@
|
|
1
|
-
#
|
2
|
-
Short description and motivation.
|
1
|
+
# Action Prompt
|
3
2
|
|
4
|
-
|
5
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
25
|
-
|
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>
|
data/lib/action_prompt/engine.rb
CHANGED
@@ -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,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
|
data/lib/action_prompt.rb
CHANGED
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.
|
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-
|
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.
|
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
|
data/lib/action_prompt/base.rb
DELETED