Add inline AI suggestions to your Django app 🗯

Photo of Tom Dekan
by Tom Dekan
Updated: Sun 02 June 2024

Using Django, we'll add inline AI suggestions to suggest helpful actions to the user.

We'll use a fast LLM, HTMX, and Photon Designer.

Here's how our final product will look:


And here's a video guide (following the below written guide) featuring me 🙂:


0. Which LLM should I use to generate AI suggestions?

First, you need to choose your LLM. The key criteria are speed and being a warm model (meaning no boot required).

I'm using Llama 3 8B. Primarily because it's warm, fast, and a negligible cost. Secondarily because I like llamas 🦙

Choose whichever model you like. As of writing, Snowflake Arctic and Mixtral are also good choices on Replicate.

We'll do this in 7 short steps. Let's go 🏎️

1. Setup Django

Create Django app and install dependencies

pip install --upgrade django python-dotenv replicate

django-admin startproject core .
python manage.py startapp sim

Add our app sim to the INSTALLED_APPS in settings.py:

# settings.py
INSTALLED_APPS = [
    'sim',
    ...
]

Add environment variables

  • Add this to the top of your settings.py to load your environment variables:
# settings.py
from pathlib import Path
import os
from dotenv import load_dotenv


load_dotenv()

if not os.environ.get('REPLICATE_API_TOKEN'):
    raise ValueError(
        'REPLICATE_API_TOKEN environment variable is not set. '
        'Please make sure you have added it to "core/.env" file, and restart the server.'
    )


  • Get your API token from Replicate
  • Create a file called .env in core and add your API token (No speech marks needed):
```.dotenv
REPLICATE_API_TOKEN=an_example_api_token

2. Add your Django app urls

  • Add the following to your core/urls.py:
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('sim.urls')),
]
  • Create a urls.py file in your sim app and add the following:
from django.contrib import admin
from django.urls import path, include
from sim import views

urlpatterns = [
    path('', views.index, name='index'),
    path('suggest', views.suggest, name='suggest'),
]


3. Add your views

  • Add the below views to your sim app (views.py):
from django.shortcuts import render
from random import randint
from django.utils.html import format_html
import replicate


def index(request):
    if request.method == 'GET':
        return render(request, 'index.html')
    elif request.method == 'POST':
        return render(request, 'index.html')


def suggest(request):
    text = request.POST.get('text', '')
    if text:
        input = {
            "prompt":
                f"Complete the following sentence. Don't write more than 1 sentence.\n----\n"
                f"{text}",
            "prompt_template": "<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n"
                               "You are a helpful assistant. Don't include the start of the sentence. "
                               "Only include your completion. "
                               "<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n{prompt}"
                               "<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n",
            "presence_penalty": 0,
            "frequency_penalty": 0
        }

        output = replicate.run(
            "meta/meta-llama-3-8b-instruct",
            input=input
        )
        completion = "".join(output)

    else:
        completion = ""

    return render(request, 'suggestion.html', {"suggestion": completion.strip()})

4. Add templates

  • Add a templates folder to your sim app
  • Add a suggestion.html file to your templates folder and add the following:
<span>
    {{ suggestion }}
</span>

  • Add a index.html files to your templates folder and add the following:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Inline Suggestion</title>
    <script src="https://unpkg.com/htmx.org@1.9.12"></script>
</head>
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>

<div>
    <form id="container" tabindex="0" hx-trigger="input delay:200ms"
          hx-post="/suggest"
          hx-target="#suggestion"

    >
        <div>
            <div id="display" contenteditable="true"
                 autofocus
                 oninput="syncInput(this.textContent);clearSuggestion();">
            </div>
            <div id="suggestion"
                 onclick="acceptSuggestion(this.textContent); clearSuggestion(); continueWriting();"
            >
                {% include 'suggestion.html' %}
            </div>
        </div>
        <input name="text" type="hidden" id="content" value="">
    </form>
</div>

<script>
    const clearSuggestion = () => {
        document.getElementById('suggestion').innerHTML = '';
        toggleSuggestionVisibility();
    }
    const acceptSuggestion = (textContent) => {
        document.getElementById('display').textContent += textContent
        syncInput(textContent)
    }
    const syncInput = (textContent) => {
        document.getElementById('content').value = textContent
    }

    const continueWriting = () => {
        document.getElementById('display').focus()
        const range = document.createRange()
        const selection = window.getSelection()
        range.selectNodeContents(document.getElementById('display'))
        range.collapse(false)
        selection.removeAllRanges()
        selection.addRange(range)
    }

    const toggleSuggestionVisibility = () => {
        const suggestionBox = document.getElementById('suggestion');
        if (suggestionBox.textContent.trim() === '') {
            suggestionBox.classList.remove('show');
            suggestionBox.classList.add('hide');
        } else {
            suggestionBox.classList.remove('hide');
            suggestionBox.classList.add('show');
        }
    }

    document.getElementById('suggestion').addEventListener('DOMNodeInserted', toggleSuggestionVisibility);
    document.getElementById('suggestion').addEventListener('DOMNodeRemoved', toggleSuggestionVisibility);
</script>

</body>
</html>

5. Run our server and visit the url

  • Let's see the unstyled version of our app:
python manage.py runserver

6. Style the UI with Photon Designer?

Currently, the UI is very basic:


I'll improve it by using 'Django Mode' in Photon Designer (my product). I pasted the code into Photon Designer, clicked 'Django Mode', and gave some prompts such "Generate amazing UI for this inline-ai suggestion component. Add masterful details. Simplify my existing code"

And the final template code (after using Photon Designer) for index.html:

<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Inline Suggestion</title>
    <script src="https://unpkg.com/htmx.org@1.9.12"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        .suggestion-box {
            transition: all 0.3s ease-in-out;
        }

        .suggestion-box.show {
            opacity: 1;
            transform: translateY(5px);
        }

        .suggestion-box.hide {
            opacity: 0;
            transform: translateY(20px);
        }
    </style>
</head>
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' class="h-full bg-gray-100">

<div class="flex justify-center items-center h-screen">
    <form id="container" tabindex="0" hx-trigger="input delay:200ms"
          hx-post="/suggest"
          hx-target="#suggestion"
          class="flex flex-col items-start w-1/2"
    >
        <div class="relative w-full">
            <div id="display" contenteditable="true"
                 class="w-full p-4 text-gray-800 bg-white border border-gray-300 rounded-lg shadow-md focus:outline-none focus:ring-1 focus:ring-indigo-300 transition-all duration-300"
                 autofocus
                 oninput="syncInput(this.textContent);clearSuggestion();">
            </div>
            <div id="suggestion"
                 class="suggestion-box absolute left-0 mb-2 bg-white border border-gray-300 rounded-lg shadow-md p-4 max-w-full text-ellipsis cursor-pointer transition-all duration-300 hide"
                 onclick="acceptSuggestion(this.textContent); clearSuggestion(); continueWriting();"
            >
                {% include 'suggestion.html' %}
            </div>
        </div>
        <input name="text" type="hidden" id="content" value="">
    </form>
</div>

<script>
    const clearSuggestion = () => {
        document.getElementById('suggestion').innerHTML = '';
        toggleSuggestionVisibility();
    }
    const acceptSuggestion = (textContent) => {
        document.getElementById('display').textContent += textContent
        syncInput(textContent)
    }
    const syncInput = (textContent) => {
        document.getElementById('content').value = textContent
    }

    const continueWriting = () => {
        document.getElementById('display').focus()
        const range = document.createRange()
        const selection = window.getSelection()
        range.selectNodeContents(document.getElementById('display'))
        range.collapse(false)
        selection.removeAllRanges()
        selection.addRange(range)
    }

    const toggleSuggestionVisibility = () => {
        const suggestionBox = document.getElementById('suggestion');
        if (suggestionBox.textContent.trim() === '') {
            suggestionBox.classList.remove('show');
            suggestionBox.classList.add('hide');
        } else {
            suggestionBox.classList.remove('hide');
            suggestionBox.classList.add('show');
        }
    }

    document.getElementById('suggestion').addEventListener('DOMNodeInserted', toggleSuggestionVisibility);
    document.getElementById('suggestion').addEventListener('DOMNodeRemoved', toggleSuggestionVisibility);
</script>

</body>
</html>

Complete ✅ AI suggestions added to your Django app

We have successfully added inline AI suggestions to our Django app. We used the Replicate API to generate the suggestions. We also improved the UI using Photon Designer. You're welcome to customize the UI further or use a different model for the suggestions.

I'll be adding this into Photon Designer to show UI prompts to users.

Here's the final product again:

Let's get visual.

Do you want to create beautiful frontends effortlessly?
Click below to book your spot on our early access mailing list (as well as early adopter prices).
Copied link to clipboard 📋

Made with care by Tom Dekan

© 2024 Photon Designer