Building an AI Photo Reviewer for Free with NextJS, Vercel AI SDK and Ollama llava-llama3 Model

Create a free AI-powered image quality analyzer using NextJS Vercel AI, Ollama, in four steps.

Image

The Vercel AI SDK has been around for a while, but I recently explored it, and it’s exciting to see what’s possible with it. This free, open-source library provides the essential tools to build AI applications — from chatbots to language translation tools, and in this case, an AI photo reviewer.

In this tutorial, you’ll learn how to build an AI Photo Reviewer that evaluates image quality and provides actionable feedback — all at no cost! We’ll use the Ollama LLaVA-Llama3 model, a vision-language AI capable of understanding and analyzing both text and visual content. The best part? You can run it locally. And if you are looking to integrate AI into your applications, you will also see where to find AI experts for your projects.

Quick Note: If you’re eager to jump straight to the code, check out the complete code on GitHub.

Sound good? Let’s dive in and build something amazing! 🚀

Prerequisites

Before beginning this tutorial, ensure you meet the following requirements:

  • Basic knowledge of Next.js.
  • Node.js installed

Step 1: Install and Configure Ollama

Ollama is a CLI tool that allows you to run large language models (LLMs) locally. You’ll use it to install and run the LLaVA-Llama3 model for image analysis.

  1. If you don’t have Ollama installed, download and install it from the official website: ollama.com. On Mac, you can install it using homebrew with the command.
brew install ollama

2. After installing, you can now install any model from the list; there are models like Phi 3, Mistral, Gemma 2, etc.; however, in this case, you are using the LLaVA-Llama3 model.

ollama pull llava-llama3

3. Optional: If you want to pull and run the model to play with it on your terminal, use this command

ollama run llava-llama3

Here is an example below of me playing with it (Note: After giving the model a task, you need to wait a few seconds for the response).

Step 2: Initialize the Next.js Project

Let’s start by creating a new Next.js project with TypeScript and Tailwind support.

  1. Open your terminal and run the following command:
npx create-next-app@latest ai-photo-reviewer
cd ai-photo-reviewer

This will scaffold a new Next.js project in a directory called ai-photo-reviewer.

2. Next, install the necessary dependencies:

npm install ai@3.2.26 ollama ollama-ai-provider react-dropzone react-markdown

Here’s a breakdown of what each package does:

  • ai: A TypeScript toolkit designed to help you build AI-powered applications
  • ollama: Provides a client for interacting with Ollama models like LLaVA-Llama3.
  • Ollama-ai-provider: A provider for managing Ollama APIs interactions.
  • react-dropzone: Simplifies file uploads with a drag-and-drop interface.
  • react-markdown: Render’s responses that may include Markdown content.

Step 3: Creating the API Route

Now, let’s create the API route that will handle requests to review images using the LLaVA-Llama3 model through the Ollama AI Provider. This route will receive an image, send it to the AI model, and return a professional photo review, focusing on key elements like color, tone, lighting, structure, and composition.

  1. Create the API file in the Next.js project at src/app/api/review/route.ts
  2. Add the following code to the file:

import { createOllama } from "ollama-ai-provider";
import { StreamingTextResponse, streamText } from "ai";

const ollama = createOllama();

export async function POST(req: Request) {
  const { encodedFiles } = await req.json();

  const result = await streamText({
    model: ollama("llava-llama3"),
    messages: \[
      {
        role: "system",
        content: \`You are an expert photography reviewer and art critic.
                  You will receive a photo and provide a detailed review focused on the following key aspects:
                  1. \*\*Color\*\*: Describe the color palette, saturation, contrast, and overall color harmony.
                  2. \*\*Tone\*\*: Evaluate the tonal range and dynamic contrast.
                  3. \*\*Lighting\*\*: Assess the quality, direction, and intensity of the lighting.
                  4. \*\*Structure\*\*: Comment on the sharpness, depth of field, and focus.
                  5. \*\*Composition\*\*: Analyze the framing, balance, rule of thirds, and overall composition.

                  Provide \*\*specific and actionable recommendations\*\* to enhance each aspect of the photo where applicable.
                  Your response should be professional and insightful, offering value to both amateur and professional photographers.\`,
      },
      {
        role: "user",
        content: \[
          {
            type: "text",
            text: "Please review this photo.",
          },
          {
            type: "image",
            image: encodedFiles\[0\], // Ensure image encoding and format is supported.
          },
        \],
      },
    \],
  });

  return new StreamingTextResponse(result.toAIStream());
}

Explanation of the Code

  • createOllama(): Initializes the Ollama AI Provider, which facilitates interaction with the LLaVA-Llama3 model.
  • POST(req: Request): Handles incoming POST requests. It extracts the image from the request, sends it to the AI model, and streams the review back to the client.
  • streamText(): Sends the messages (image and instructions) to the AI model and streams the output in real-time.
  • StreamingTextResponse: Wraps the AI stream response and sends it back to the client as a streaming response for real-time updates.

Now that your API is ready let’s move on to building the user interface to upload photos and display the AI-generated reviews!

Step 4: Building the Frontend

Now that we have set up the API route, let’s build the frontend for our AI Photo Reviewer. The interface will allow users to upload an image, display it, and show the AI-generated review in real time.

On the page.tsx file, add the following code:

"use client";
import { useState, useEffect } from "react";

import { useChat } from "ai/react";
import Markdown from "react-markdown";
import { useDropzone } from "react-dropzone";

export default function Review() {
  const \[encodedFiles, setEncodedFiles\] = useState`<string\[\]>`(\[\]);
  const { getRootProps, getInputProps } = useDropzone({
    onDropAccepted: async (files) => {
      const getBase64 = async (file: Blob): Promise`<string>` => {
        var reader = new FileReader();
        reader.readAsDataURL(file as Blob);

        return new Promise((reslove, reject) => {
          reader.onload = () => reslove(reader.result as any);
          reader.onerror = (error) => reject(error);
        });
      };
      const eFiles: string\[\] = \[\];
      for (const file of files) {
        eFiles.push(await getBase64(file));
      }
      setEncodedFiles(eFiles);
    },
  });
  const { messages, handleSubmit, isLoading } = useChat({
    body: {
      encodedFiles,
    },
  });

  useEffect(() => {
    if (
      !isLoading &&
      encodedFiles.length > 0 &&
      !messages.find(({ role }) => role !== "user")
    ) {
      handleSubmit();
    }
  }, \[isLoading, encodedFiles, messages, handleSubmit\]);

  return (
    `<div className="min-h-screen bg-gray-300 flex items-center justify-center py-10">`
      `<div className="bg-white shadow-lg rounded-lg p-8 max-w-4xl w-full">`
        `<div className="grid grid-cols-1 md:grid-cols-2 gap-8">`
          {/\* Dropzone & Image Preview \*/}
          `<section className="border-2 border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center gap-4 bg-gray-50 hover:bg-gray-100 transition">`
            {encodedFiles.length === 0 ? (
              `<div
                {...getRootProps({
                  className:
                    'dropzone w-full h-32 flex flex-col justify-center items-center cursor-pointer',
                })}
              >`
                `<input {...getInputProps()} />`
                `<p className="text-lg text-gray-600 font-medium">`
                  Drag & drop your photo here or click to select
                `</p>`
              `</div>`
            ) : (
              `<div className="grid grid-cols-1 gap-4">`
                {encodedFiles.map((file, i) => (
                  `<img
                    src={file}
                    key={i}
                    className="rounded-lg shadow-md object-cover w-full"
                  />`
                ))}
              `</div>`
            )}
          `</section>`

          {/\* Chat Output \*/}
          `<div className="flex flex-col gap-4">`
            {isLoading && (
              `<div className="flex items-center justify-center">`
                `<div className="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12">``</div>`
                `<p className="text-gray-600 font-semibold ml-4">`
                  Describing your image... please wait.
                `</p>`
              `</div>`
            )}

            {messages.map((m) => (
              `<div
                key={m.id}
                className="bg-gray-100 p-4 rounded-lg shadow-md text-gray-800"
              >`
                {m.role !== 'user' && `<Markdown>`{m.content}`</Markdown>`}
              `</div>`
            ))}
          `</div>`
        `</div>`
      `</div>`
    `</div>`
  );
}

Explanation of the Code

  • useDropzone: This is used to handle file uploads via drag-and-drop functionality. It converts uploaded images to Base64 format to be sent to the API.
  • useChat: A hook from the Vercel AI SDK that manages API interactions and handles the AI model’s streaming response.
  • handleSubmit(): Automatically sends the uploaded image to the AI model once the file is dropped.
  • Markdown: Used to render the AI’s response in a readable format, with support for rich text elements.

Let’s style the frontend a bit by adding this to the global.css file:

.loader {
  border-top-color: #3498db;
  animation: spin 1s infinite linear;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

Testing the AI Photo Reviewer App

Now that we’ve built both the backend API and the frontend interface, it’s time to test your AI Photo Reviewer application.

npm run dev

Then, open your browser and navigate to http://localhost:3000. You should see the user interface with a drag-and-drop area that prompts you to upload a photo.

Finally, Drag and drop an image file (JPEG or PNG format) or click to select a file from your computer. Wait for a few seconds for the response, which should look like this.

Where do you find AI experts for your projects?

Just like this, you can easily integrate AI features like chatbot, AI-powered search, personalization, etc, into your applications to dramatically improve user experience, automate tasks, and provide data-driven insights that enhance decision-making. For business owners, adding AI-driven functionalities can increase efficiency, improve customer engagement, and open new revenue streams.

If you’re looking to integrate AI into your project but need expert assistance, consider hiring a vetted freelancer from Fiverr Pro. Fiverr Pro offers a curated marketplace of highly skilled professionals in AI development, web design, and more, ensuring you get top-tier talent to bring your vision to life.

What’s Next?

Congratulations! You have successfully built an AI photo reviewer with Next.js, Vercel AI SDK, and Ollama LLaVA-Llama3! You’ve seen firsthand how easy it is to create a tool that evaluates image quality and provides actionable feedback. This project not only showcases the potential of AI but also demonstrates how quickly you can now build functional AI applications.

You can expand the project further by adding features like multi-image support, text-to-speech feature, etc. If you are a founder or business owner who wants to integrate AI into your applications, you can find vetted expert freelancers on Fiverr Pro who can elevate your products.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics