Skip to content

Commit

Permalink
initial working version of stacks
Browse files Browse the repository at this point in the history
  • Loading branch information
yujonglee committed Nov 7, 2024
1 parent 5d1cb5b commit 13b51e9
Show file tree
Hide file tree
Showing 17 changed files with 389 additions and 6 deletions.
12 changes: 12 additions & 0 deletions core/assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ const hooks = {
...getHooks(Components),
LiveToast: createLiveToastHook(),
Prompt: window.Prompt,
EnterToSubmit: {
mounted() {
this.el.addEventListener("keydown", (e) => {
if (e.key == "Enter") {
e.preventDefault();
this.el.form.dispatchEvent(
new Event("submit", { bubbles: true, cancelable: true }),
);
}
});
},
},
Highlight: {
mounted() {
this.run();
Expand Down
3 changes: 3 additions & 0 deletions core/lib/canary_web/components/layouts/stacks.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<main>
<%= @inner_content %>
</main>
26 changes: 26 additions & 0 deletions core/lib/canary_web/live/stacks_live/index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule CanaryWeb.StacksLive.Index do
use CanaryWeb, :live_view

@impl true
def render(%{live_action: :selector} = assigns) do
~H"""
<div>
<.live_component id="stacks-selector" module={CanaryWeb.StacksLive.Selector} />
</div>
"""
end

@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end

@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end

defp apply_action(socket, :selector, _params) do
socket
end
end
115 changes: 115 additions & 0 deletions core/lib/canary_web/live/stacks_live/selector.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
defmodule CanaryWeb.StacksLive.Selector do
use CanaryWeb, :live_component

@items [
%{
name: "next-forge",
logo_url: "/images/next-forge.svg"
},
%{
name: "Turborepo",
logo_url: "/images/turborepo.svg"
},
%{
name: "Vercel",
logo_url: "/images/vercel.svg"
},
%{
name: "Next.js",
logo_url: "/images/nextjs.svg",
project_id: "TODO",
tabs: []
},
%{
name: "next-seo",
logo_url: "/images/seo.svg"
},
%{
name: "Shadcn",
logo_url: "/images/shadcn.svg",
project_id: "TODO",
tabs: []
},
%{
name: "React Email",
logo_url: "/images/nextjs.svg",
project_id: "TODO",
tabs: []
},
%{
name: "Prisma",
logo_url: "/images/prisma.png",
project_id: "TODO",
tabs: []
},
%{
name: "Clerk",
logo_url: "/images/clerk.png",
project_id: "TODO",
tabs: []
},
%{
name: "Stripe",
logo_url: "/images/stripe.svg",
project_id: "TODO",
tabs: []
}
]
@impl true
def render(assigns) do
~H"""
<div class="flex flex-col items-center justify-center">
<div class="flex flex-col items-center my-4">
<h1 class="text-3xl font-bold">
Ask about
<.link href="https://github.com/haydenbleasel/next-forge" class="underline">
Next-Forge
</.link>
</h1>
<p>
Brought to you by <a href="https://github.com/fastrepl/canary" target="_blank">🐤 Canary</a>
</p>
<p class="mt-4">
All public sources about next-forge are indexed.
</p>
</div>
<div>
<div class="grid grid-cols-5 gap-4">
<%= for item <- @items do %>
<div class="flex flex-col gap-2 items-center justify-center">
<img src={item.logo_url} class="w-24 h-24 object-contain" />
<p class="text-sm text-gray-500">
<%= item.name %>
</p>
</div>
<% end %>
</div>
<.button is_primary phx-target={@myself} phx-click="start" class="mt-4 w-full h-12">
Get started
</.button>
</div>
</div>
"""
end

@impl true
def update(assigns, socket) do
socket =
socket
|> assign(assigns)
|> assign(:items, @items)

{:ok, socket}
end

@impl true
def handle_event("start", _, socket) do
id = Ecto.UUID.generate()
data = %{dataset_id: "prod_1feac9f6-f1f0-4f16-b0c3-6daa823ea7b3"}
Cachex.put(:cache, id, data, ttl: :timer.hours(1))

{:noreply, push_navigate(socket, to: "/stacks/nextforge/#{id}")}
end
end
166 changes: 166 additions & 0 deletions core/lib/canary_web/live/stacks_live/session.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
defmodule CanaryWeb.StacksLive.Session do
use CanaryWeb, :live_view

alias Canary.Index.Trieve

@impl true
def render(assigns) do
~H"""
<div class="flex flex-col max-w-4xl mx-auto h-screen pt-4">
<h1 class="text-xl font-semibold mb-4">next-forge</h1>
<div class="flex-grow overflow-y-auto flex flex-col gap-2">
<div :for={message <- @messages} class="flex flex-row gap-2 items-center">
<span class="text-gray-500"><%= message.role %></span>
<span class="text-gray-900"><%= message.content %></span>
</div>
</div>
<.form :let={f} for={@form} phx-change="validate" phx-submit="submit" class="relative mt-4">
<.input
id="session-form"
phx-hook="EnterToSubmit"
type="textarea"
field={f[:message]}
placeholder="Ask a question"
class="h-32 w-full"
/>
<.button type="submit" class="absolute right-2 bottom-2">↵</.button>
</.form>
</div>
"""
end

@impl true
def mount(%{"id" => id}, _session, socket) do
{:ok, %{dataset_id: id}} = Cachex.get(:cache, id)

socket =
socket
|> assign(:form, message_form())
|> assign(:messages, [])
|> assign(:client, Trieve.client(id))

{:ok, socket}
end

defp message_form(params \\ %{}) do
data = %{}
types = %{message: :string}

{data, types}
|> Ecto.Changeset.cast(params, Map.keys(types))
|> Ecto.Changeset.validate_required([:message])
|> Ecto.Changeset.validate_length(:message, min: 1)
|> to_form(as: :form)
end

@impl true
def handle_event("validate", %{"form" => params}, socket) do
form = message_form(params)
{:noreply, assign(socket, :form, form)}
end

@impl true
def handle_event("submit", %{"form" => params}, socket) do
messages =
socket.assigns.messages ++
[
%{role: :user, content: params["message"]},
%{role: :assistant, content: ""}
]

here = self()
client = socket.assigns.client

socket =
socket
|> assign(:form, message_form())
|> assign(:messages, messages)
|> start_async(:generation, fn -> run(here, client, params["message"]) end)

{:noreply, socket}
end

@impl true
def handle_info({:delta, content}, socket) do
messages =
socket.assigns.messages
|> update_in([Access.at(-1), :content], &(&1 <> content))

{:noreply, assign(socket, :messages, messages)}
end

@impl true
def handle_async(:generation, _d, socket) do
{:noreply, socket}
end

defp run(here, client, query) do
{:ok, groups} = client |> Trieve.search(query)

results =
groups
|> Enum.take(8)
|> Enum.map(fn %{"chunks" => chunks, "group" => %{"tracking_id" => group_id}} ->
Task.async(fn ->
chunk_indices =
chunks |> Enum.map(&get_in(&1, ["chunk", "metadata", Access.key("index", 0)]))

case Trieve.get_chunks(client, group_id, chunk_indices: chunk_indices) do
{:ok, %{"chunks" => full_chunks}} ->
full_chunks
|> Enum.map(fn chunk ->
%{
"url" => chunk["link"],
"content" => chunk["chunk_html"],
"metadata" => chunk["metadata"]
}
end)

_ ->
nil
end
end)
end)
|> Task.await_many(3_000)
|> Enum.reject(&is_nil/1)

Canary.AI.chat(
%{
model: Application.fetch_env!(:canary, :responder_model),
messages: [
%{
role: "system",
content: Canary.Prompt.format("responder_system", %{})
},
%{
role: "user",
content: """
<retrieved_documents>
#{Jason.encode!(results)}
</retrieved_documents>
<user_question>
#{query}
</user_question>
"""
}
],
temperature: 0,
frequency_penalty: 0.02,
max_tokens: 5000,
stream: true
},
callback: fn data ->
case data do
%{"choices" => [%{"delta" => %{"content" => content}}]} ->
send(here, {:delta, content})

_ ->
:ok
end
end
)
end
end
7 changes: 7 additions & 0 deletions core/lib/canary_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ defmodule CanaryWeb.Router do
live "/experiment", CanaryWeb.ExperimentLive.Index, :none
end

ash_authentication_live_session :stacks,
layout: {CanaryWeb.Layouts, :stacks},
on_mount: [] do
live "/stacks/nextforge", CanaryWeb.StacksLive.Index, :selector
live "/stacks/nextforge/:id", CanaryWeb.StacksLive.Session, :none
end

ash_authentication_live_session :others,
layout: {CanaryWeb.Layouts, :root},
on_mount: [{CanaryWeb.LiveUser, :live_user_required}] do
Expand Down
Binary file added core/priv/static/images/clerk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 0 additions & 6 deletions core/priv/static/images/logo.svg

This file was deleted.

1 change: 1 addition & 0 deletions core/priv/static/images/next-forge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions core/priv/static/images/nextjs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added core/priv/static/images/prisma.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions core/priv/static/images/seo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions core/priv/static/images/shadcn.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions core/priv/static/images/stripe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions core/priv/static/images/tailwind.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 13b51e9

Please sign in to comment.