Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
df8e44d
Parser module and Bubble parsing
tmonks Mar 4, 2024
14c0ed0
Refactor ChatLive to use Bubbles
tmonks Mar 5, 2024
2c9492d
Parser parses Images, and improved Bubbles
tmonks Mar 7, 2024
081bce1
Add Req
tmonks Mar 8, 2024
1c87037
Add StabilityAi client
tmonks Mar 8, 2024
524a113
update download_path for prod to /priv/static
tmonks Mar 9, 2024
8c41994
Image struct
tmonks Mar 9, 2024
3fc482e
Rename ChatApi to OpenAi.Api
tmonks Mar 9, 2024
c6993f1
Fix broken tests
tmonks Mar 9, 2024
26674c6
ChatLive displays new images as loading
tmonks Mar 11, 2024
7f248a5
Parser splits responses on newline
tmonks Mar 11, 2024
58bf7a1
ChatLive requests image
tmonks Mar 16, 2024
5203ac3
Parser returns ImageRequest instead of Image
tmonks Mar 17, 2024
1dd7d96
ChatLive retrieves and displays images
tmonks Mar 18, 2024
71d58f3
Simplify :request_image handler fn
tmonks Mar 19, 2024
d6e2079
Parser can parse ImageResponse without text
tmonks Mar 20, 2024
b8a25f4
Fix image path
tmonks Mar 25, 2024
77bfc3d
Remove Chat struct and work with messages directly
tmonks Mar 25, 2024
f789d5e
ChatApi.send_message takes just a list of messages
tmonks Mar 28, 2024
8dd2eda
Parser.parse_image_response
tmonks Mar 31, 2024
4824888
Parser can parse an image from a message with role "image"
tmonks Apr 2, 2024
e33031c
send_message takes a list of messages and returns a message
tmonks Apr 4, 2024
ee6c4fe
ChatLive just uses a single list of messages
tmonks Apr 4, 2024
8574108
Generate Chat and Message schemas
tmonks Apr 6, 2024
06c0a45
Set up Chat and Message assocs
tmonks Apr 7, 2024
5a769df
Chats.create_chat
tmonks Apr 7, 2024
129b0ba
Setup ExMachina
tmonks Apr 9, 2024
4ba7e3e
Chats.get_chat! fn
tmonks Apr 10, 2024
71ee249
create_message and other context fn tests
tmonks Apr 11, 2024
425ac9f
OpenAI.Api works with maps instead of Messages
tmonks Apr 13, 2024
3f5bdfa
Improve Chats.create_message
tmonks Apr 14, 2024
e8a43b3
First pass at making ChatLive load chat from db
tmonks Apr 14, 2024
704441c
Fix broken tests to load chat from db
tmonks Apr 16, 2024
c8c05db
Set up HomeLive modules
tmonks Apr 18, 2024
6de91e5
list_chats fn
tmonks Apr 24, 2024
ecf9e52
Add timex
tmonks Apr 24, 2024
6f144d0
list_chats preloads latest_message for each chat
tmonks Apr 24, 2024
1c18df9
list_chats preloads the bot
tmonks Apr 26, 2024
2431944
HomeLive lists chats with bot names
tmonks Apr 26, 2024
2040857
Show relative time of last message
tmonks Apr 26, 2024
6b1632b
Show latest message text for each chat
tmonks Apr 26, 2024
4a0e648
Add bot selector to HomeLive
tmonks Apr 26, 2024
92eae4c
HomeLive can create new chat
tmonks Apr 29, 2024
7ad6764
Link to existing chats
tmonks Apr 30, 2024
700f17c
First pass at HomeLive design improvement
tmonks May 2, 2024
81424cf
Add bot icon
tmonks May 5, 2024
8c2ac62
Store errors and image messages to db
tmonks May 9, 2024
a70bf6b
Add test for parsing an image_prompt (no UI content)
tmonks May 10, 2024
105c887
Parse and show ImageRequests
tmonks May 14, 2024
76f1f8d
Test for parsing multiple elements from single message
tmonks May 15, 2024
e1f763b
Parser can parse a Choice
tmonks May 15, 2024
32bc2fc
ChatLive can render a Choice as buttons
tmonks May 16, 2024
804eb49
Clicking a button sends the choice to the chat API
tmonks May 17, 2024
f1d12d0
Make choice click use submit_message event
tmonks May 18, 2024
8459d2c
Show text before choices
tmonks May 18, 2024
c034a70
Adjust bot bubble and button colors/layout
tmonks May 19, 2024
682eccd
Add DayJob and CYOA prompts to the seeder
tmonks May 20, 2024
ee21ad0
Hide ImageRequests in ChatLive
tmonks May 21, 2024
1f69090
Consistent spacing between chat items
tmonks May 21, 2024
dcb39ae
Fix skipped tests
tmonks May 21, 2024
b036587
Fix loading animation not turning off after image gen
tmonks May 22, 2024
83c98a9
Config persistent storage for generated images on Fly
tmonks May 25, 2024
012ae49
Fixed column widths on HomeLive
tmonks May 27, 2024
415deb9
Remove delimiters from CYOA bot
tmonks May 27, 2024
c6bd209
Make HomeLive handle previews of different message types
tmonks May 30, 2024
d84f316
Add json_mode to bot
tmonks Jun 20, 2024
697abf4
send_message takes a bot and handles json response option
tmonks Jun 20, 2024
53f671c
Split bubbles on single newline
tmonks Jun 20, 2024
ddb4bec
Fix 2 broken tests
tmonks Jun 21, 2024
51ddcb4
create_bot function and unique index on name
tmonks Jun 23, 2024
f2a75bd
create_bot upserts on name conflict
tmonks Jun 23, 2024
1cd5466
Fix create_bot so that it doesn't replace id or inserted_at
tmonks Jun 25, 2024
ca27e59
Update Seeder to upsert bots
tmonks Jun 25, 2024
d52050b
Fix parser handling of double newlines
tmonks Jun 27, 2024
db67728
Sort chats newest to oldest
tmonks Jun 27, 2024
0bd7661
Update TriviaQuiz prompt for difficulty and accuracy
tmonks Jun 27, 2024
41bb4e5
Add spacing between chat and text box
tmonks Jul 3, 2024
4f16789
Tweak TriviaQuiz prompt
tmonks Jul 3, 2024
f1890ed
set json_mode on bots
tmonks Jul 5, 2024
b9030bc
Update bot changeset to cast json_mode
tmonks Jul 7, 2024
c0c33be
First draft of Buzz Feed quiz bot
tmonks Jul 10, 2024
6e08aa6
Add image prompting to FuzzBeed bot
tmonks Jul 11, 2024
b4f7264
Elaborate negative prompt for SD
tmonks Jul 12, 2024
537f254
Improve spacing, button layout, and bot bubble color
tmonks Jul 12, 2024
2f5a5ad
Update FuzzBeed to clarify required attributes
tmonks Jul 15, 2024
9cb6d67
Update FuzzBeed to allow subsequent quizzes
tmonks Jul 17, 2024
727aea7
Update workflow to deploy from any branch
tmonks Jul 21, 2024
e4a9334
Add mix task and fly release_command
tmonks Jul 22, 2024
a322caa
Call Seeder directly in release_command
tmonks Jul 23, 2024
441ecea
Try using Release to seed from release_command
tmonks Jul 23, 2024
4b3ecc4
Try running seeds in release via Code.eval_file
tmonks Jul 25, 2024
7ae2b1a
Try calling Seeder in Release using with_repo
tmonks Jul 26, 2024
26dc709
Fix function call in with_repo
tmonks Jul 26, 2024
139acf8
Fix function call in with_repo, again
tmonks Jul 26, 2024
88c56ca
Remove release_command and add update_bots to app start
tmonks Jul 27, 2024
4345d0e
Add ChristmasCritters bot
tmonks Dec 16, 2024
0d9e63a
Tweak IdeaGenie prompt to distinguish categories and ideas
tmonks Mar 18, 2025
5068e9c
Vertically align option buttons on all screens
tmonks Mar 18, 2025
93fc842
Switch to gpt-4o-mini model
tmonks Mar 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: Fly Deploy
on:
push:
branches:
- main

jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
concurrency: deploy-group # ensure only one action runs at a time
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ npm-debug.log
/assets/node_modules/

.env
chatbots*.db*
chatbots*.db*
priv/static/images/*
8 changes: 6 additions & 2 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Config
config :chat_bots, ChatBotsWeb.Endpoint,
# Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
http: [ip: {127, 0, 0, 1}, port: 4000],
http: [ip: {127, 0, 0, 1}, port: 4001],
check_origin: false,
code_reloader: true,
debug_errors: true,
Expand Down Expand Up @@ -53,7 +53,8 @@ config :chat_bots, ChatBots.Repo,
config :chat_bots, ChatBotsWeb.Endpoint,
live_reload: [
patterns: [
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
# ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/static/.*(js|css)$",
~r"lib/chat_bots_web/(controllers|live|components)/.*(ex|heex)$"
]
]
Expand All @@ -70,3 +71,6 @@ config :phoenix, :stacktrace_depth, 20

# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime

# path for downloading images
config :chat_bots, download_path: "priv/static/images/generated"
6 changes: 6 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ config :openai,
organization_key: env!("OPENAI_ORG_KEY"),
http_options: [recv_timeout: 30_000]

# Stability.ai configuration
config :chat_bots, stability_ai_api_key: env!("STABILITY_AI_API_KEY")

if config_env() == :prod do
# The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you
Expand Down Expand Up @@ -69,6 +72,9 @@ if config_env() == :prod do
# get password from env (will raise if not set)
config :chat_bots, :auth, password: System.fetch_env!("USER_PASSWORD")

# path for downloading generated images
config :chat_bots, download_path: "/data/generated-images"

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
Expand Down
3 changes: 3 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ config :logger, level: :warning

# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime

# set download path for generated images to /tmp
config :chat_bots, :download_path, "/tmp"
5 changes: 3 additions & 2 deletions lib/chat_bots/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ defmodule ChatBots.Application do
opts = [strategy: :one_for_one, name: ChatBots.Supervisor]
result = Supervisor.start_link(children, opts)

# Temporarily reset the database on start during development
ChatBots.Seeder.reset()
# Update the bots
ChatBots.Seeder.update_bots()

result
end

Expand Down
12 changes: 12 additions & 0 deletions lib/chat_bots/bots.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,16 @@ defmodule ChatBots.Bots do
|> where([b], b.id == ^id)
|> Repo.one()
end

@doc """
create_bot/1 creates a new bot with the given attributes.
"""
def create_bot(attrs) do
%Bot{}
|> Bot.changeset(attrs)
|> Repo.insert(
on_conflict: {:replace_all_except, [:id, :inserted_at]},
conflict_target: :name
)
end
end
4 changes: 3 additions & 1 deletion lib/chat_bots/bots/bot.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ defmodule ChatBots.Bots.Bot do
schema "bots" do
field(:directive, :string)
field(:name, :string)
field(:json_mode, :boolean, default: false)

timestamps()
end

@doc false
def changeset(bot, attrs) do
bot
|> cast(attrs, [:name, :directive])
|> cast(attrs, [:name, :directive, :json_mode])
|> validate_required([:name, :directive])
|> unique_constraint(:name)
end
end
47 changes: 0 additions & 47 deletions lib/chat_bots/chat_api.ex

This file was deleted.

70 changes: 65 additions & 5 deletions lib/chat_bots/chats.ex
Original file line number Diff line number Diff line change
@@ -1,21 +1,81 @@
defmodule ChatBots.Chats do
alias ChatBots.Chats.{Chat, Message}
alias ChatBots.Bots
alias ChatBots.Chats.Chat
alias ChatBots.Chats.Message
alias ChatBots.Repo

import Ecto.Changeset
import Ecto.Query

@doc """
Creates a new chat for the given bot_id.
Adds a message with the bot's system prompt.
"""
def create_chat(bot) do
%Chat{}
|> change(bot_id: bot.id)
|> put_assoc(:messages, [%Message{role: "system", content: bot.directive}])
|> Repo.insert!()
end

@doc """
Retrieves a chat by id
"""
def get_chat!(id) do
Repo.get!(Chat, id)
|> Repo.preload([:messages, :bot])
end

@doc """
Creates a new message for the given chat
"""
def create_message(chat, attrs) do
%Message{}
|> cast(attrs, [:role, :content])
|> put_assoc(:chat, chat)
|> Repo.insert()
end

@doc """
Creates a new chat with the given bot_id.
"""
def new_chat(bot_id) do
bot = Bots.get_bot(bot_id)
system_prompt = %Message{role: "system", content: bot.directive}
%Chat{bot_id: bot_id, messages: [system_prompt]}
[system_prompt]
end

@doc """
Adds a message to the chat.
"""
# TODO: enforce that it only accepts a %Message struct
def add_message(chat, message) do
%Chat{chat | messages: chat.messages ++ [message]}
def add_message(messages, message) do
messages ++ [message]
end

@doc """
Lists all chats
Preloads messages
"""
def list_chats do
preload_query = preload_latest_message_query()

from(c in Chat,
order_by: [desc: :inserted_at],
preload: [:bot, :messages, latest_message: ^preload_query]
)
|> Repo.all()
end

defp preload_latest_message_query do
ranking_query =
from(m in Message,
select: %{id: m.id, row_number: over(row_number(), :message_partition)},
windows: [message_partition: [partition_by: :chat_id, order_by: [desc: m.inserted_at]]]
)

from(m in Message,
join: r in subquery(ranking_query),
on: m.id == r.id and r.row_number == 1
)
end
end
3 changes: 3 additions & 0 deletions lib/chat_bots/chats/bubble.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule ChatBots.Chats.Bubble do
defstruct [:type, :text]
end
13 changes: 12 additions & 1 deletion lib/chat_bots/chats/chat.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
defmodule ChatBots.Chats.Chat do
defstruct [:bot_id, :messages]
use Ecto.Schema
alias ChatBots.Bots.Bot
alias ChatBots.Chats.Message

@primary_key {:id, :binary_id, autogenerate: true}
schema "chats" do
belongs_to(:bot, Bot)
has_many(:messages, Message)
has_one(:latest_message, Message)

timestamps()
end
end
3 changes: 3 additions & 0 deletions lib/chat_bots/chats/choice.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule ChatBots.Chats.Choice do
defstruct [:options]
end
3 changes: 3 additions & 0 deletions lib/chat_bots/chats/image.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule ChatBots.Chats.Image do
defstruct [:file, :prompt]
end
3 changes: 3 additions & 0 deletions lib/chat_bots/chats/image_request.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule ChatBots.Chats.ImageRequest do
defstruct [:prompt]
end
21 changes: 20 additions & 1 deletion lib/chat_bots/chats/message.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
defmodule ChatBots.Chats.Message do
defstruct [:role, :content]
use Ecto.Schema
import Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "messages" do
field(:role, :string)
field(:content, :string)

belongs_to(:chat, ChatBots.Chats.Chat)

timestamps()
end

@doc false
def changeset(message, attrs) do
message
|> cast(attrs, [:role, :content])
|> validate_required([:role, :content])
end
end
41 changes: 41 additions & 0 deletions lib/chat_bots/open_ai/api.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule ChatBots.OpenAi.Api do
alias ChatBots.OpenAi.Client

@model "gpt-4o-mini"

@doc """
Sends a message to the chat bot and returns the updated chat.
"""
def send_message(bot, messages) do
params = prepare_params(bot, messages)

case Client.chat_completion(params) do
{:ok, %{choices: [choice | _]}} ->
{:ok, choice["message"]}

{:error, :timeout} ->
{:error, %{"message" => "Your request timed out"}}

{:error, error} ->
{:error, error["error"]}
end
end

defp prepare_params(bot, messages) do
[messages: messages]
|> add_model()
|> maybe_add_json_mode(bot)
end

defp add_model(params) do
params ++ [model: @model]
end

defp maybe_add_json_mode(params, bot) do
if bot.json_mode do
params ++ [response_format: %{type: "json_object"}]
else
params
end
end
end
4 changes: 2 additions & 2 deletions lib/chat_bots/open_ai/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ defmodule ChatBots.OpenAi.Client do
@callback chat_completion(model: String.t(), messages: [Message.t()]) ::
{:ok, map()} | {:error, map()}

def chat_completion(model: model, messages: messages) do
client().chat_completion(model: model, messages: messages)
def chat_completion(options) do
client().chat_completion(options)
end

defp client do
Expand Down
4 changes: 2 additions & 2 deletions lib/chat_bots/open_ai/http_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule ChatBots.OpenAi.HttpClient do
@behaviour ChatBots.OpenAi.Client

@impl true
def chat_completion(model: model, messages: messages) do
OpenAI.chat_completion(model: model, messages: messages)
def chat_completion(params) do
OpenAI.chat_completion(params)
end
end
Loading