Packages

fnord

0.5.0

AI code archaeology

Current section

Files

Jump to
fnord lib store note.ex
Raw

lib/store/note.ex

defmodule Store.Note do
defstruct [
:project,
:store_path,
:id
]
@store_dir "notes"
# ----------------------------------------------------------------------------
# Instance functions
# ----------------------------------------------------------------------------
def new(project), do: new(project, UUID.uuid4())
def new(project, nil), do: new(project, UUID.uuid4())
def new(project, id) do
%__MODULE__{
project: project,
store_path: build_store_path(project, id),
id: id
}
end
def exists?(note) do
File.exists?(note.store_path)
end
def write(note, text) do
# Ensure the note's store path exists.
note.store_path
|> File.mkdir_p!()
# Has the note changed since the last save?
is_changed? =
with {:ok, orig} <- read_note(note) do
orig != text
else
_ -> true
end
if is_changed? do
# Write the note's text to the note's store path.
note.store_path
|> Path.join("note.md")
|> File.write!(text)
# Generate and save embeddings for the note.
embeddings_json =
text
|> generate_embeddings!()
|> Jason.encode!()
note.store_path
|> Path.join("embeddings.json")
|> File.write!(embeddings_json)
end
end
def read(note) do
with {:ok, text} <- read_note(note),
{:ok, embeddings} <- read_embeddings(note) do
{:ok,
%{
id: note.id,
text: text,
embeddings: embeddings
}}
end
end
def read_note(note) do
note.store_path
|> Path.join("note.md")
|> File.read()
end
def read_embeddings(note) do
note.store_path
|> Path.join("embeddings.json")
|> File.read()
|> case do
{:ok, json} -> Jason.decode(json)
error -> error
end
end
# ----------------------------------------------------------------------------
# Common functions
# ----------------------------------------------------------------------------
def list_notes(project) do
project.store_path
|> Path.join(@store_dir)
|> File.ls()
|> case do
{:ok, dirs} ->
dirs
|> Enum.sort()
|> Enum.map(&new(project, &1))
_ ->
[]
end
end
def search(project, query, max_results \\ 20) do
needle = generate_embeddings!(query)
list_notes(project)
|> Enum.reduce([], fn note, acc ->
with {:ok, embeddings} <- read_embeddings(note) do
score = AI.Util.cosine_similarity(needle, embeddings)
[{score, note} | acc]
else
_ -> acc
end
end)
|> Enum.sort(fn {a, _}, {b, _} -> a >= b end)
|> Enum.take(max_results)
end
def reset_project_notes(project) do
project
|> build_store_path()
|> File.rm_rf()
end
# ----------------------------------------------------------------------------
# Private functions
# ----------------------------------------------------------------------------
defp build_store_path(project) do
project.store_path
|> Path.join(@store_dir)
end
defp build_store_path(project, id) do
project
|> build_store_path()
|> Path.join(id)
end
defp generate_embeddings!(text) do
AI.new()
|> AI.get_embeddings(text)
|> case do
{:ok, embeddings} -> Enum.zip_with(embeddings, &Enum.max/1)
{:error, reason} -> raise "Failed to generate embeddings: #{inspect(reason)}"
end
end
end