Packages
fnord
0.8.82
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/cmd/conversations.ex
defmodule Cmd.Conversations do
@behaviour Cmd
@impl Cmd
def requires_project?(), do: true
@impl Cmd
def spec do
[
conversations: [
name: "conversations",
about: "List all conversations in the project",
options: [
project: Cmd.project_arg(),
prune: [
value_name: "PRUNE",
long: "--prune",
short: "-P",
help: "Prune conversations older than this many days",
parser: :integer
],
query: [
value_name: "QUERY",
long: "--query",
short: "-q",
help: "Semantic search query",
parser: :string
],
limit: [
value_name: "LIMIT",
long: "--limit",
short: "-l",
help: "Max search results",
parser: :integer,
default: 5
]
]
]
]
end
@impl Cmd
def run(%{prune: days} = opts, _subcommands, _unknown) when is_integer(days) do
with {:ok, project} <- Store.get_project() do
case prune(opts, project) do
:ok -> :ok
{:error, :cancelled} -> UI.error("Operation cancelled.")
{:error, :invalid_prune_value} -> UI.error("Invalid --prune value: #{days}")
end
else
{:error, :project_not_set} ->
UI.error("No project selected; use --project or run in a project directory.")
end
end
@impl Cmd
def run(%{query: query} = opts, _subcommands, _unknown) when is_binary(query) do
with {:ok, project} <- Store.get_project() do
search(opts, project)
else
{:error, :project_not_set} ->
UI.error(
"No project selected; please specify --project or run inside a project directory."
)
end
end
@impl Cmd
def run(opts, _subcommands, _unknown) do
with {:ok, project} <- Store.get_project(),
:ok <- display(opts, project) do
:ok
else
{:error, :project_not_set} ->
UI.error(
"No project selected; please specify --project or run inside a project directory."
)
end
end
defp prune(%{prune: days}, project) when is_integer(days) and days >= 0 do
cutoff = DateTime.utc_now() |> DateTime.add(-days, :day)
UI.info("Pruning conversations older than #{days} days")
to_delete =
project
|> Store.Project.conversations()
|> Enum.reduce([], fn conversation, acc ->
timestamp = Store.Project.Conversation.timestamp(conversation)
if DateTime.compare(timestamp, cutoff) == :lt do
[conversation | acc]
else
acc
end
end)
if to_delete == [] do
UI.info("No conversations to prune.")
:ok
else
UI.info("Preparing to delete the following conversations:")
to_delete
|> Enum.each(fn conversation ->
UI.info(conversation.id)
end)
if UI.confirm("Confirm deletion of the listed conversations. This action cannot be undone.") do
to_delete
|> Enum.each(fn conversation ->
Store.Project.Conversation.delete(conversation)
Store.Project.ConversationIndex.delete(project, conversation.id)
end)
count = length(to_delete)
UI.info("Deleted #{count} conversation(s).")
:ok
else
{:error, :cancelled}
end
end
end
defp prune(%{prune: days}, _project) when is_integer(days) and days < 0 do
{:error, :invalid_prune_value}
end
defp prune(_opts, _project), do: :ok
defp display(_opts, project) do
project
|> Store.Project.conversations()
|> case do
[] ->
UI.puts("No conversations found.")
conversations ->
conversations
|> Enum.map(fn conversation ->
%{
id: conversation.id,
timestamp: Store.Project.Conversation.timestamp(conversation),
file: conversation.store_path,
question: get_question(conversation),
length: Store.Project.Conversation.num_messages(conversation)
}
end)
|> Jason.encode!(pretty: true)
|> UI.puts()
end
:ok
end
defp search(opts, project) do
query = Map.get(opts, :query)
limit = Map.get(opts, :limit, 5)
case Search.Conversations.search(project, query, limit: limit) do
{:ok, results} ->
Enum.each(results, &UI.puts(print_search_row(&1)))
:ok
{:error, reason} ->
UI.error("Search failed: #{inspect(reason)}")
end
end
defp print_search_row(%{
conversation_id: id,
score: score,
timestamp: timestamp,
title: title,
length: length
}) do
[
format_score(score),
format_timestamp(timestamp),
to_string(length),
to_string(id),
truncate_title(title)
]
|> Enum.join("\t")
end
defp truncate_title(title) when is_binary(title) do
case String.split(title, "\n", parts: 2) do
[first] -> first
[first, _rest] -> first <> "..."
end
end
defp format_score(score) when is_number(score) do
:erlang.float_to_binary(score, decimals: 3)
end
defp format_timestamp(timestamp) do
case timestamp do
%DateTime{} ->
unix = DateTime.to_unix(timestamp)
{cmd, args} =
case :os.type() do
{:unix, :darwin} ->
{"date", ["-r", to_string(unix), "+%Y-%m-%d %H:%M:%S"]}
{:unix, _} ->
{"date", ["-d", "@#{unix}", "+%Y-%m-%d %H:%M:%S"]}
end
case System.cmd(cmd, args, stderr_to_stdout: true) do
{result, 0} -> String.trim(result)
_ -> DateTime.to_string(timestamp)
end
other ->
to_string(other)
end
end
defp get_question(conversation) do
with {:ok, question} <- Store.Project.Conversation.question(conversation) do
question
else
{:error, :no_question} -> "(not found)"
end
end
end