diff --git a/apps/cf/lib/actions/action_creator.ex b/apps/cf/lib/actions/action_creator.ex index 8f99586c..96f19e16 100644 --- a/apps/cf/lib/actions/action_creator.ex +++ b/apps/cf/lib/actions/action_creator.ex @@ -218,6 +218,10 @@ defmodule CF.Actions.ActionCreator do ) end + def action_start_automatic_statements_extraction(user_id, video_id) do + action(user_id, :video, :start_automatic_statements_extraction, video_id: video_id) + end + def action_email_confirmed(user_id) do admin_action(:user, :email_confirmed, target_user_id: user_id) end diff --git a/apps/cf/lib/algolia/StatementsIndex.ex b/apps/cf/lib/algolia/StatementsIndex.ex index 6a3be273..6fdd8dca 100644 --- a/apps/cf/lib/algolia/StatementsIndex.ex +++ b/apps/cf/lib/algolia/StatementsIndex.ex @@ -26,7 +26,7 @@ defmodule CF.Algolia.StatementsIndex do """ @impl Algoliax.Indexer def to_be_indexed?(statement) do - not statement.is_removed + not (statement.is_removed or statement.is_draft) end @impl Algoliax.Indexer diff --git a/apps/cf/lib/llms/statements_creator.ex b/apps/cf/lib/llms/statements_creator.ex index 2e918397..5005b198 100644 --- a/apps/cf/lib/llms/statements_creator.ex +++ b/apps/cf/lib/llms/statements_creator.ex @@ -64,7 +64,7 @@ defmodule CF.LLMs.StatementsCreator do Enum.chunk_every(captions, @captions_chunk_size) end - defp get_llm_suggested_statements(video, captions, retries \\ 0) do + defp get_llm_suggested_statements(video, captions, retries \\ 5) do OpenAI.chat_completion( model: Application.get_env(:cf, :openai_model), response_format: %{type: "json_object"}, @@ -135,10 +135,11 @@ defmodule CF.LLMs.StatementsCreator do Enum.map(statements_inputs, fn %{"text" => text, "time" => time} -> %{ video_id: video.id, - text: text, + text: CF.Utils.truncate(text, 280), time: time, inserted_at: inserted_at, - updated_at: inserted_at + updated_at: inserted_at, + is_draft: true } end), returning: true diff --git a/apps/cf/lib/utils/utils.ex b/apps/cf/lib/utils/utils.ex index 2cdea4d3..26fd91f8 100644 --- a/apps/cf/lib/utils/utils.ex +++ b/apps/cf/lib/utils/utils.ex @@ -36,6 +36,14 @@ defmodule CF.Utils do def map_string_keys_to_atom_keys(value), do: value + def truncate(text, max_length, replacement \\ "…") do + if String.length(text) > max_length do + String.slice(text, 0, max_length - String.length(replacement)) <> replacement + else + text + end + end + # Convert key to atom if key is in binary format defp convert_key_to_atom(key) when is_binary(key), diff --git a/apps/cf_graphql/lib/resolvers/videos.ex b/apps/cf_graphql/lib/resolvers/videos.ex index 79888753..141e8245 100644 --- a/apps/cf_graphql/lib/resolvers/videos.ex +++ b/apps/cf_graphql/lib/resolvers/videos.ex @@ -100,8 +100,17 @@ defmodule CF.Graphql.Resolvers.Videos do |> Enum.group_by(& &1.video_id) end - def start_automatic_statements_extraction(_root, %{video_id: video_id}, _info) do + def start_automatic_statements_extraction(_root, %{video_id: video_id}, %{ + context: %{user: user} + }) do video = DB.Repo.get!(DB.Schema.Video, video_id) + + # Record a `UserAction` + user.id + |> CF.Actions.ActionCreator.action_start_automatic_statements_extraction(video.id) + |> DB.Repo.insert!() + + # Start the extraction process CF.LLMs.StatementsCreator.process_video!(video.id) {:ok, video} end diff --git a/apps/cf_graphql/lib/schema/input_objects/statement_filter.ex b/apps/cf_graphql/lib/schema/input_objects/statement_filter.ex index 8ab95b3b..212901cf 100644 --- a/apps/cf_graphql/lib/schema/input_objects/statement_filter.ex +++ b/apps/cf_graphql/lib/schema/input_objects/statement_filter.ex @@ -9,6 +9,7 @@ defmodule CF.Graphql.Schema.InputObjects.StatementFilter do @desc "Props to filter statements on" input_object :statement_filter do field(:commented, :boolean) + field(:is_draft, :boolean) field(:speaker_id, :id) end end diff --git a/apps/cf_graphql/lib/schema/types/statement.ex b/apps/cf_graphql/lib/schema/types/statement.ex index 6f2e502e..1ea1f78c 100644 --- a/apps/cf_graphql/lib/schema/types/statement.ex +++ b/apps/cf_graphql/lib/schema/types/statement.ex @@ -16,6 +16,9 @@ defmodule CF.Graphql.Schema.Types.Statement do field(:text, non_null(:string)) @desc "Statement timecode, in seconds" field(:time, non_null(:integer)) + @desc "Whether the statement is in draft mode" + field(:is_draft, non_null(:boolean)) + @desc "Statement's speaker. Null if statement describes picture" field :speaker, :speaker do resolve(assoc(:speaker)) diff --git a/apps/cf_rest_api/lib/views/statement_view.ex b/apps/cf_rest_api/lib/views/statement_view.ex index 41d2915d..b30c4a56 100644 --- a/apps/cf_rest_api/lib/views/statement_view.ex +++ b/apps/cf_rest_api/lib/views/statement_view.ex @@ -18,7 +18,8 @@ defmodule CF.RestApi.StatementView do id: statement.id, text: statement.text, time: statement.time, - speaker_id: statement.speaker_id + speaker_id: statement.speaker_id, + is_draft: statement.is_draft } end @@ -27,6 +28,7 @@ defmodule CF.RestApi.StatementView do id: statement.id, text: statement.text, time: statement.time, + is_draft: statement.is_draft, speaker: render_one(statement.speaker, CF.RestApi.SpeakerView, "speaker.json"), comments: render_many(statement.comments, CF.RestApi.CommentView, "comment.json") } diff --git a/apps/db/lib/db_schema/statement.ex b/apps/db/lib/db_schema/statement.ex index 994343d5..23030d2e 100644 --- a/apps/db/lib/db_schema/statement.ex +++ b/apps/db/lib/db_schema/statement.ex @@ -10,6 +10,7 @@ defmodule DB.Schema.Statement do field(:text, :string) field(:time, :integer) field(:is_removed, :boolean, default: false) + field(:is_draft, :boolean, default: false) belongs_to(:video, DB.Schema.Video) belongs_to(:speaker, DB.Schema.Speaker) @@ -20,7 +21,7 @@ defmodule DB.Schema.Statement do end @required_fields ~w(text time video_id)a - @optional_fields ~w(speaker_id)a + @optional_fields ~w(speaker_id is_draft)a # Define queries @@ -57,6 +58,12 @@ defmodule DB.Schema.Statement do {:commented, true}, query -> from(s in query, inner_join: c in assoc(s, :comments), group_by: s.id) + {:is_draft, true}, query -> + from(s in query, where: s.is_draft == true) + + {:is_draft, false}, query -> + from(s in query, where: s.is_draft == false) + {:speaker_id, nil}, query -> from(s in query, where: is_nil(s.speaker_id)) diff --git a/apps/db/lib/db_type/user_action_type.ex b/apps/db/lib/db_type/user_action_type.ex index 5e8f4449..3fba7623 100644 --- a/apps/db/lib/db_type/user_action_type.ex +++ b/apps/db/lib/db_type/user_action_type.ex @@ -26,6 +26,7 @@ defenum( # Special actions email_confirmed: 100, collective_moderation: 101, + start_automatic_statements_extraction: 102, # Deprecated. Can safelly be re-used action_banned: 102, abused_flag: 103, diff --git a/apps/db/priv/repo/migrations/20240915080224_add-draft-to-statements.exs b/apps/db/priv/repo/migrations/20240915080224_add-draft-to-statements.exs new file mode 100644 index 00000000..0af5d3ab --- /dev/null +++ b/apps/db/priv/repo/migrations/20240915080224_add-draft-to-statements.exs @@ -0,0 +1,9 @@ +defmodule :"Elixir.DB.Repo.Migrations.Add-draft-to-statements" do + use Ecto.Migration + + def change do + alter table(:statements) do + add :is_draft, :boolean, default: false, null: false + end + end +end