AI prompts
base on Let's bake some (AI) stickers! # StickerBaker
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="en" dir="ltr">Announcing StickerBaker!<br><br>Make stickers with AI. Powered by <a href="https://twitter.com/replicate?ref_src=twsrc%5Etfw">@replicate</a> and <a href="https://twitter.com/flydotio?ref_src=twsrc%5Etfw">@flydotio</a>, and 100% open-source.<a href="https://t.co/8vucCsHtAd">https://t.co/8vucCsHtAd</a> <a href="https://t.co/tBhDyGrOx0">pic.twitter.com/tBhDyGrOx0</a></p>— Charlie Holtz (@charliebholtz) <a href="https://twitter.com/charliebholtz/status/1762232726361633018?ref_src=twsrc%5Etfw">February 26, 2024</a></blockquote>
## How it works
Enter a prompt and generating a sticker using https://replicate.com/fofr/sticker-maker.
Here's an overview of the architecture:
![](./architecture.png)
The home page is rendered in `lib/sticker_web/home_live.ex`. When the prompt form is submitted, this handle_event gets called:
```elixir
def handle_event("save", %{"prompt" => prompt}, socket) do
user_id = socket.assigns.local_user_id
{:ok, prediction} =
Predictions.create_prediction(%{
prompt: prompt,
local_user_id: user_id
})
send(self(), {:kick_off, prediction})
{:noreply,
socket
|> assign(form: to_form(%{"prompt" => ""}))
|> stream_insert(:my_predictions, prediction, at: 0)}
end
```
This sends a `:kick_off` message to the LiveView (so there is no lag) which calls `Predictions.moderate/3` in `lib/sticker/predictions.ex`:
```elixir
@doc """
Moderates a prediction.
The logic in replicate_webhook_controller.ex handles
the webhook. Once the moderation is complete, the webhook controller automatically
called gen_image.
"""
def moderate(prompt, user_id, prediction_id) do
"fofr/prompt-classifier"
|> Replicate.Models.get!()
|> Replicate.Models.get_latest_version!()
|> Replicate.Predictions.create(
%{
prompt: "[PROMPT] #{prompt} [/PROMPT] [SAFETY_RANKING]",
max_new_tokens: 128,
temperature: 0.2,
top_p: 0.9,
top_k: 50,
stop_sequences: "[/SAFETY_RANKING]"
},
"#{Sticker.Utils.get_host()}/webhooks/replicate?user_id=#{user_id}&prediction_id=#{prediction_id}"
)
end
```
We pass a webhook to [Replicate](https://replicate.com). All the logic for the webhook lives in `lib/sticker_web/controllers/replicate_webhook_controller.ex`. The nice thing about this webhook is that we can refresh the page or disconnect and [Replicate](https://replicate.com) still handles the prediction queue for us. Once the prediction is ready,
we upload it to [Tigris](https://fly.io/docs/reference/tigris/) (Replicate doesn't save our data for us) and then the sticker gets broadcast back to our `home_live.ex`.
**Importantly**, because we're passing Replicate a webhook, for local dev you'll need [ngrok](https://ngrok.com) running to tunnel your localhost to a URL. Once you install ngrok run it with `ngrok http 4000` and paste the URL into your copied `.env` file.
## Stack
StickerBaker runs on:
- [Replicate](https://replicate.com/fofr/sticker-maker?utm_source=project&utm_campaign=stickerbaker) to generate the stickers
- [Fly.io](https://fly.io) for infrastructure
- [Tigris](https://www.tigrisdata.com) for image hosting
## Dev
To start your Phoenix server:
- Run `mix setup` to install and setup dependencies
- Create an env file with `cp .env.copy .env`
- Add your [Replicate](https://replicate.com) tokens
- Add [Tigris](https://fly.io/docs/reference/tigris/) tokens
- Start ngrok with `ngrok http 4000` and add that to your env
- Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
- Add a .env file with REPLICATE_API_TOKEN set to your [Replicate](https://replicate.com/) token
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
## Prod
Update the `url` and `check_origin` origin in `prod.exs`
Deploy with `fly launch`
Make sure when you `fly launch` you set up a Postgres DB!
", Assign "at most 3 tags" to the expected json: {"id":"8166","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"