-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
389 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<main> | ||
<%= @inner_content %> | ||
</main> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file was deleted.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.