diff --git a/README.md b/README.md index ac7ab0f..ce93ec8 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,10 @@ Let's use `Elixir` machine learning capabilities to build an application that performs **image captioning** -and **semantic search** -to search for uploaded images +and **semantic searching** +to look for uploaded images with your voice! 🎙️ -
- -
-+ +
@@ -3171,13 +3183,18 @@ and all of the code inside the [`_comparison`](./_comparison/) folder. +
@@ -3416,12 +3433,14 @@ so this part of your code will shrink to:
```elixir
-
+ + + 🔥 LiveView + + + + + 🐝 Bumblebee + + +
+ + ++ Caption your image! +
++ Upload your own image (up to 5MB) and perform image captioning with + + Elixir + + ! +
++ Powered with + + HuggingFace🤗 + + transformer models, + you can run this project locally and perform machine learning tasks with a handful lines of code. +
+ + +or drag and drop
+PNG, JPG, GIF up to 5MB
+ <% end %> ++ Record a phrase or some key words. + We'll detect them and semantically search it in our database of images! +
++ After recording your audio, you can listen to it. It will be transcripted automatically into text and appear below. +
++ Semantic search will automatically kick in and the resulting image will be shown below. +
+ + + + + ++ +
+ + ++ +
+ + ## _Please_ star the repo! ⭐️ If you find this package/repo useful, diff --git a/assets/css/app.css b/assets/css/app.css index b0e8da4..9c81d72 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -17,3 +17,10 @@ body { height: 100vh; width: 100vw; } + +/* Override the container displays to both be shown on larger screens */ +@media (min-width: 1024px) { + #upload_container, #search_container { + display: block !important; /* Override any inline styles */ + } +} \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js index 9334602..a93f0c4 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -89,6 +89,39 @@ let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken }, }); +// Toggles to show upload or semantic search containers +// JavaScript to toggle visibility and styles +document.getElementById('upload_option').addEventListener('click', function() { + document.getElementById('upload_container').style.display = 'block'; + document.getElementById('search_container').style.display = 'none'; + + document.getElementById('upload_option').classList.replace('bg-white', 'bg-blue-500'); + document.getElementById('upload_option').classList.replace('text-gray-900', 'text-white'); + document.getElementById('upload_option').classList.replace('hover:bg-gray-50', 'hover:bg-blue-600'); + document.getElementById('upload_option').getElementsByTagName('svg')[0].classList.replace('text-gray-400', 'text-white'); + + document.getElementById('search_option').classList.replace('bg-blue-500', 'bg-white'); + document.getElementById('search_option').classList.replace('text-white', 'text-gray-900'); + document.getElementById('search_option').classList.replace('hover:bg-blue-600', 'hover:bg-gray-50'); + document.getElementById('search_option').getElementsByTagName('svg')[0].classList.replace('text-white', 'text-gray-400'); +}); + +document.getElementById('search_option').addEventListener('click', function() { + document.getElementById('upload_container').style.display = 'none'; + document.getElementById('search_container').style.display = 'block'; + + document.getElementById('search_option').classList.replace('bg-white', 'bg-blue-500'); + document.getElementById('search_option').classList.replace('text-gray-900', 'text-white'); + document.getElementById('search_option').classList.replace('hover:bg-gray-50', 'hover:bg-blue-600'); + document.getElementById('search_option').getElementsByTagName('svg')[0].classList.replace('text-gray-400', 'text-white'); + + document.getElementById('upload_option').classList.replace('bg-blue-500', 'bg-white'); + document.getElementById('upload_option').classList.replace('text-white', 'text-gray-900'); + document.getElementById('upload_option').classList.replace('hover:bg-blue-600', 'hover:bg-gray-50'); + document.getElementById('upload_option').getElementsByTagName('svg')[0].classList.replace('text-white', 'text-gray-400'); +}); + + // Show progress bar on live navigation and form submits topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }); window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)); diff --git a/lib/app_web/live/page_live.ex b/lib/app_web/live/page_live.ex index a44583b..7b9483b 100644 --- a/lib/app_web/live/page_live.ex +++ b/lib/app_web/live/page_live.ex @@ -199,10 +199,10 @@ defmodule AppWeb.PageLive do )} # Otherwise, if there was an error uploading the image, we log the error and show it to the person. - %{error: errors} -> - Logger.warning("⚠️ Error uploading image. #{inspect(errors)}") - {:noreply, push_event(socket, "toast", %{message: "Image couldn't be uploaded to S3"})} - end + %{error: error} -> + Logger.warning("⚠️ Error uploading image. #{inspect(error)}") + {:noreply, push_event(socket, "toast", %{message: "Image couldn't be uploaded to S3.\n#{error}"})} + end end # This function is called whenever a user records their voice. @@ -244,7 +244,7 @@ defmodule AppWeb.PageLive do def handle_progress(_, _, socket), do: {:noreply, socket} @doc """ - Called in `handle_progress` to Handle the upload to the bucket and returns the format `{:ok, map}` or {:postpone, message}` + Called in `handle_progress` to handle the upload to the bucket and returns the format `{:ok, map}` or {:postpone, message}` as demanded by the signature of callback function used `consume_uploaded_entry` """ def handle_upload({:ok, %{path: path, tensor: tensor, image_info: image_info} = map}) @@ -275,10 +275,8 @@ defmodule AppWeb.PageLive do end @doc """ - Every time an `async task` is created, this function is called. - We destructure the output of the task and update the socket assigns. - - This function handles both the image that is uploaded by the user and the example images. + This function is invoked after the async task for embedding model is completed. + It retrieves the embedding, normalizes it and knn-searches the embedding in our database. """ @impl true def handle_info({ref, %{chunks: [%{text: text}]} = _result}, %{assigns: assigns} = socket) diff --git a/lib/app_web/live/page_live.html.heex b/lib/app_web/live/page_live.html.heex index 97977e7..e00312c 100644 --- a/lib/app_web/live/page_live.html.heex +++ b/lib/app_web/live/page_live.html.heex @@ -1,232 +1,299 @@ -+
- - 🔥 LiveView - - + - - 🐝 Bumblebee - - -
-- Caption your image! -
-- Powered with + + - HuggingFace🤗 + > + 🐝 Bumblebee - transformer models, - you can run this project locally and perform machine learning tasks with a handful lines of code. +
-