author: Marcus A. Lee
published on: Dec. 27, 2024, 11:51 p.m.
updated on: Dec. 29, 2024, 4:33 p.m.
tags: #Web Development
We'll be building an AI-Powered Search Bar using Django. This project will not only give you a sleek search interface but also integrate some cool AI functionalities to enhance your user experience. If you're just diving into the AI Agents and LLMs, you'll find this guide both approachable and packed with valuable insights.
A search bar that not only fetches results but also chats with your database, offering recommendations and insights on the fly.
By the end of this tutorial, you'll have hands-on experience with the DjangoAIAssistant library. We'll walk through setting up a basic AI workflow that manages both an active search feature and a talk-to-database capability using HTMX and three AI Agents (AI Assistants).
To bring this project to life, we'll be leveraging a bunch of awesome libraries:
Alright, let's kick things off by setting up our Django project and installing the necessary libraries.
Set Up Virtual Environment:
python -m venv venv
Activate Virtual Environment:
Windows:
venv\Scripts\activate
macOS/Linux:
source venv/bin/activate
Install Django:
pip install django
Create Django Project:
django-admin startproject ai_search_project
Navigate to Project Directory and Create App
cd ai_search_project
python manage.py startapp search_app
Connect the App to the Project:
Edit ai_search_project/settings.py
:
INSTALLED_APPS = [
# ... other installed apps
'search_app',
]
Update ai_search_project/urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('search_app.urls')),
]
Next up, we'll install and configure the necessary frontend libraries.
Install TailwindCSS: Follow the installation steps and keep the default theme name theme
.
Navigate to TailwindCSS Directory and Install DaisyUI:
cd theme/static/static_src/
npm i -D daisyui@latest
Update tailwind.config.js
for DaisyUI:
module.exports = {
content: [
// other codes
],
daisyui: {
themes: ["light", "dark", "cupcake"],
},
theme: {
extend: {},
},
plugins: [
// other codes
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
require('daisyui'),
],
}
Now, let's set up our HTML templates with TailwindCSS and DaisyUI for a beautiful UI.
Update base.html
to Include HTMX and DaisyUI:
{% load static tailwind_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{% block title %}{% endblock %}</title>
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.13"></script>
{% tailwind_css %}
</head>
<body data-theme="cupcake" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<header>
</header>
<main>
{% block content %}
{% endblock %}
</main>
<footer>
</footer>
{% block extra_body %}
<!-- Additional body elements or scripts can be added in child templates -->
{% endblock %}
</body>
</html>
Set DaisyUI Theme to cupcake
:
<body data-theme="cupcake" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
Create index.html
in search_app/templates/search_app/
:
{% extends 'base.html' %}
{% block title %}AI Search Bar{% endblock title %}
{% block content %}
<div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center">
<div class="card w-full bg-base-100 shadow-xl">
<div class="card-body">
<h1 class="card-title text-3xl justify-center mb-6">AI Search Bar</h1>
<div class="form-control">
<div class="input-group space-y-2">
<input
id="prompt_input"
name="prompt_input"
type="text"
placeholder="Enter your search query"
class="input input-bordered w-full"
hx-post="{% url 'active_search'%}"
hx-trigger="input changed delay:250ms"
hx-target="#search-results"
hx-swap="innerHTML">
<button class="btn btn-primary">
search
</button>
</div>
</div>
<div
id="search-results"
class="border-2 border-secondary rounded-lg w-full">
{% include 'search_app/partials/active_search_results.html' %}
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
Create Partial Template active_search_results.html
:
<div id="search-results" class="bg-base-200 border-2 border-base-300 rounded-lg shadow-xl relative">
<button
id="close-search"
class="absolute top-0 right-0 p-1 bg-base-300 text-primary rounded-full shadow-md hover:bg-primary hover:text-base-300 transition-all duration-300 transform hover:scale-110"
hx-get="{% url 'active_search' %}"
hx-target="#search-results"
hx-swap="innerHTML"
_="on click
set #prompt_input.value to ''
add .hidden to me
put '' into #search-results">
Close
</button>
<div id="ai-search-results" class="space-y-4 p-4">
{% if results %}
<!-- "Ask AI" Section -->
<div
id="ask_ai"
class="flex items-center gap-4 bg-base-100 p-4 rounded-lg shadow-sm hover:bg-base-200 transition-colors duration-200 cursor-pointer"
hx-post="{% url 'ask_ai' %}"
hx-swap="innerHTML"
hx-target="#ai-search-results"
hx-include="#ask_ai_input"
hx-trigger="click"
hx-indicator="#ai-initial-loading">
<div class="flex items-center gap-4">
<!-- Optional Icon or Placeholder -->
<div id="ai-initial-loading" class="htmx-indicator my-2 items-center">
<span class="htmx-indicator-content flex">
AI is thinking...
</span>
<span class="loading loading-spinner loading-md"></span>
</div>
<div>
<h3 class="text-lg font-semibold text-primary">Ask AI</h3>
<p class="text-sm text-base-content/80">{{ search_content }}</p>
<input id="ask_ai_input" name="ask_ai_input" class="hidden" value="{{ search_content }}">
</div>
</div>
</div>
<!-- Divider -->
<div class="border-t border-base-300"></div>
<!-- Product Results -->
{% for shoe in results %}
<a
href=""
class="flex items-center gap-4 p-4 bg-base-100 rounded-lg hover:bg-base-300 transition-colors duration-200">
<figure class="px-10 pt-10 rounded-lg">
<img
src="https://img.daisyui.com/images/stock/photo-1606107557195-0e29a4b5b4aa.webp"
alt="Shoes" />
</figure>
<div class="flex flex-col text-left">
<h3 class="text-lg font-semibold text-primary">{{ shoe.brand }}</h3>
<h4 class="text-md text-primary mb-1">{{ shoe.model }}</h4>
<p class="text-sm text-base-content/70">
{{ shoe.description|truncatewords:20 }}
</p>
<div class="flex items-center gap-4 mt-2 text-xs text-base-content/50">
<span class="items-center text-center">đź“… {{ shoe.year_produced }}</span>
<span class="items-center text-center">🆔 {{ shoe.sku_code }}</span>
<span class="items-center text-center">🌟 {{ shoe.rarity }}</span>
</div>
</div>
</a>
{% endfor %}
{% else %}
<!-- No Results Found -->
<div
id="ask_ai"
class="flex items-center gap-4 bg-base-100 p-4 rounded-lg shadow-sm hover:bg-base-200 transition-colors duration-200 cursor-pointer"
hx-post="{% url 'ask_ai' %}"
hx-swap="innerHTML"
hx-target="#ai-search-results"
hx-include="#ask_ai_input"
hx-trigger="click"
hx-indicator="#ai-initial-loading">
<div class="flex items-center gap-4">
<!-- Optional Icon or Placeholder -->
<div id="ai-initial-loading" class="htmx-indicator my-2 items-center">
<span class="htmx-indicator-content flex">
AI is thinking...
</span>
<span class="loading loading-spinner loading-md"></span>
</div>
<div>
<h3 class="text-lg font-semibold text-primary">Ask AI</h3>
<p class="text-sm text-base-content/80">{{ search_content }}</p>
<input id="ask_ai_input" name="ask_ai_input" class="hidden" value="{{ search_content }}">
</div>
</div>
</div>
<p class="text-center text-base-content/50">No related products found.</p>
{% endif %}
</div>
</div>
Update settings.py
to Include the App's Directory:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
BASE_DIR / "templates",
BASE_DIR / "search_app",
],
...
}
Let's get our backend logic in place to handle search queries and display results.
Create Views in search_app/views.py
:
from django.template.response import TemplateResponse
from django.http import HttpResponse
from search_app.models import CollectableShoe
import bleach
from django.db.models import Q
def index(request) -> HttpResponse:
return TemplateResponse(request, "search_app/index.html")
def active_search(request) -> HttpResponse:
search_content = request.POST.get("prompt_input", "")
print("SEARCH CONTENT: ", search_content)
results = []
if not search_content:
# if the search content is empty, return an empty response, to handle the keyup event of deleting the search content
return HttpResponse("") # pyright: ignore
# Sanitize the search content using bleach
sanitized_content = bleach.clean(
search_content.strip(), strip=True
) # TODO: Add https://pypi.org/project/bleach/
# Remove any remaining non-alphanumeric characters (except spaces)
sanitized_content = "".join(
char for char in sanitized_content if char.isalnum() or char.isspace()
)
search_content = sanitized_content
# Filter CollectableShoes based on search_content
results = CollectableShoe.objects.filter(
Q(brand__icontains=search_content)
| Q(model__icontains=search_content)
| Q(collaboration_artist__icontains=search_content)
| Q(sku_code__icontains=search_content)
| Q(color__icontains=search_content)
| Q(rarity__icontains=search_content)
).distinct()
context = {
"results": results,
"search_content": search_content,
}
return TemplateResponse(
request, "search_app/partials/active_search_results.html", context
)
Create the Model in search_app/models.py
:
from django.db import models
from mdeditor.fields import MDTextField
# Create your models here.
class CollectableShoe(models.Model):
RARITY_CHOICES = [
("C", "Common"),
("U", "Uncommon"),
("R", "Rare"),
("VR", "Very Rare"),
("UR", "Ultra Rare"),
]
brand = models.CharField(max_length=100)
model = models.CharField(max_length=100)
retail_price = models.DecimalField(max_digits=10, decimal_places=2)
year_produced = models.PositiveIntegerField()
collaboration_artist = models.CharField(max_length=100, blank=True, null=True)
sku_code = models.CharField(max_length=50, unique=True)
market_price = models.DecimalField(max_digits=10, decimal_places=2)
quantity_on_release = models.PositiveIntegerField()
color = models.CharField(max_length=50)
size = models.DecimalField(max_digits=4, decimal_places=1)
rarity = models.CharField(max_length=2, choices=RARITY_CHOICES)
description = models.TextField(blank=True, null=True)
def __str__(self):
return f"{self.brand} {self.model} - {self.color} (Size: {self.size})"
Update admin.py
to Register the Model:
from django.contrib import admin
from .models import CollectableShoe
admin.site.register(CollectableShoe)
Create a Superuser to Access the Admin Console and Add Data:
python manage.py createsuperuser
Run Migrations and Test the Component:
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
Time to add some AI magic! We'll set up AI Assistants that can interact with our shoe database, providing background info and recommendations. Here is an overview of the workflow we going to create;
The AI Agents Workflow is structured around the central AISearchAssistant acting as the "Conductor". Here’s how the system works:
Install mdeditor
:
pip install django-mdeditor
Follow the installation instructions to set it up.
Update the Model to Hold Shoe Descriptions:
from django.db import models
from mdeditor.fields import MDTextField
# Create your models here.
class CollectableShoe(models.Model):
RARITY_CHOICES = [
("C", "Common"),
("U", "Uncommon"),
("R", "Rare"),
("VR", "Very Rare"),
("UR", "Ultra Rare"),
]
brand = models.CharField(max_length=100)
model = models.CharField(max_length=100)
retail_price = models.DecimalField(max_digits=10, decimal_places=2)
year_produced = models.PositiveIntegerField()
collaboration_artist = models.CharField(max_length=100, blank=True, null=True)
sku_code = models.CharField(max_length=50, unique=True)
market_price = models.DecimalField(max_digits=10, decimal_places=2)
quantity_on_release = models.PositiveIntegerField()
color = models.CharField(max_length=50)
size = models.DecimalField(max_digits=4, decimal_places=1)
rarity = models.CharField(max_length=2, choices=RARITY_CHOICES)
description = models.TextField(blank=True, null=True)
def __str__(self):
return f"{self.brand} {self.model} - {self.color} (Size: {self.size})"
class ShoeDescription(models.Model):
shoe = models.OneToOneField(CollectableShoe, on_delete=models.CASCADE)
description = MDTextField()
def __str__(self):
return f"Description for {self.shoe}"
Run Migrations and Update admin.py
:
python manage.py makemigrations
python manage.py migrate
from django.contrib import admin
from .models import CollectableShoe, ShoeDescription
admin.site.register(CollectableShoe)
admin.site.register(ShoeDescription)
Create the Bio of Collectable Shoes: For this demo, we'll use ChatGPT to generate a markdown bio for our shoes. Here's an example for the Reebok Answer III:
# Answer III Basketball Shoes
## Overview
The Answer III Basketball Shoes are a perfect blend of classic design and modern performance, drawing inspiration from the legacy of basketball legend Allen Iverson. These iconic shoes celebrate Iverson’s impact on the court and his influence on streetwear culture, making them a must-have for collectors and athletes alike.
---
## Design and Features
### **Colors**
- **Primary:** White
- **Accents:** Vintage Chalk and Vector Red
The clean white base provides a sleek and versatile look, while the vintage chalk detailing adds a retro touch. The vector red highlights bring a bold, dynamic flair to the design, reflecting the energy and passion Iverson brought to the game.
### **Materials**
- **Upper:** Premium synthetic leather for durability and style.
- **Midsole:** Lightweight EVA foam for enhanced comfort and support during gameplay.
- **Outsole:** Rubber with a multidirectional traction pattern for optimal grip and agility.
### **Fit and Comfort**
Designed to provide a snug yet flexible fit, the Answer III shoes feature an adjustable lacing system and padded collar, ensuring maximum comfort for extended wear. The interior lining wicks away moisture, keeping feet dry and comfortable.
---
## History and Legacy
The Answer III Basketball Shoes are a reimagined version of the original shoes worn by Allen Iverson during the early 2000s. This model pays homage to his fearless playing style and his rise as one of the most iconic figures in basketball history. The design encapsulates Iverson's personal style and his trailblazing impact on the sport, making it a timeless collector’s item.
---
## What’s Special About Them?
- **Heritage Design:** Inspired by Allen Iverson’s legendary career, these shoes are a tribute to his unstoppable drive and skill on the court.
- **Collector’s Appeal:** With their bold design and historical significance, the Answer III shoes are highly sought after by sneaker enthusiasts and basketball fans.
- **Exclusivity:** This item is excluded from promotions, Hero discounts, and Student discounts, emphasizing its premium status and limited availability.
---
## Price
- **Retail Price:** $140
The Answer III Basketball Shoes offer excellent value for collectors and players, combining cutting-edge performance features with a rich history.
---
## Common Questions
### **Who is the artist or player associated with these shoes?**
The Answer III shoes are inspired by Allen Iverson, a Hall of Fame basketball player known for his electrifying playing style and cultural impact.
### **What makes these shoes unique?**
These shoes stand out for their connection to basketball history, their exclusive design details, and their limited availability.
### **Are these shoes good for playing basketball?**
Yes, the Answer III shoes are designed for both performance and style, featuring a durable build, superior traction, and comfortable cushioning.
### **Can I use discounts or promotions to purchase these shoes?**
No, this item is excluded from all promotions, including the Hero and Student discounts.
### **What occasions are these shoes suitable for?**
These shoes are perfect for basketball games, casual wear, and as a collector’s item for display.
### **Are they limited edition?**
While not explicitly marketed as limited edition, their exclusion from discounts and promotions suggests exclusivity, making them a valuable addition to any sneaker collection.
---
The Answer III Basketball Shoes are more than just footwear—they are a piece of basketball history, a fashion statement, and a testament to the legacy of Allen Iverson.
Now, let's integrate AI Assistants to make our search bar smarter!
NOTE: You have to be logged in to chat with the AI since each thread is tied to a user.
Create ai_Assistant.py
in search_app/
:
from django_ai_assistant import AIAssistant, method_tool
from pydantic import BaseModel, Field
from search_app.models import CollectableShoe, ShoeDescription
from typing import Sequence
from langchain_core.tools import BaseTool
from langchain_community.retrievers import TFIDFRetriever
from langchain_core.retrievers import BaseRetriever
from langchain_text_splitters import RecursiveCharacterTextSplitter
import json
# Structured Output for Shoe Recommendation Assistant
class AssistantResponse(BaseModel):
"""Response including conversational text and shoe component."""
response: str = Field(description="A conversational response to the user's query")
shoe_component: str = Field(
description="the context for shoe recommendation",
)
# AI Assistant: TOOL
class ShoeRecommendationAssistant(AIAssistant):
id = "shoe_recommendation_assistant" # noqa: A003
name = "Shoe Recommendation Assistant"
instructions = (
"You are an Assistant to support answering questions related to collectable shoes in your database. "
"You must only provide recommendations based on the available shoes you have access to. "
"You do not engage in conversations that are not related to your available shoes. "
"Use the provided functions to answer queries and run operations.\n"
"Maintain a friendly and casual tone. If no information is available for the user's questions, "
"state that you don't have that specific detail. "
"Your response must be in markdown syntax, here is an example: \n"
"A conversational response to the user's query\n"
"### {brand} {model} \n"
"Color: {color}\n"
"SKU: {sku_code}\n"
"Rarity: {rarity}\n"
"Collaboration Artist: {collaboration_artist}\n"
"Year Produced: {year_produced}\n"
"Retail Price: ${retail_price}\n"
"Market Price: ${market_price}\n"
)
model = "gpt-4o-mini"
tool_max_concurrency = 4
structured_output = AssistantResponse
def get_instructions(self):
shoe_data_json = self.get_shoes()
return "\n".join(
[
self.instructions,
f"Available Shoes: {shoe_data_json}",
]
)
@method_tool()
def get_shoes(self) -> str:
"""Get what shoes are in the database."""
shoes = CollectableShoe.objects.all()
if not shoes:
return "Empty"
shoe_data_list = []
for shoe in shoes:
shoe_info = {
"brand": shoe.brand,
"model": shoe.model,
"color": shoe.color,
"description": shoe.description,
"sku_code": shoe.sku_code,
"rarity": shoe.get_rarity_display(),
"collaboration_artist": shoe.collaboration_artist,
"year_produced": shoe.year_produced,
"retail_price": float(shoe.retail_price),
"market_price": float(shoe.market_price),
"quantity_on_release": shoe.quantity_on_release,
"size": float(shoe.size),
}
shoe_data_list.append(shoe_info)
return json.dumps(shoe_data_list)
# AI Assistant: RAG
class RAGShoeAssistant(AIAssistant):
id = "rag_shoe_assistant" # noqa: A003
name = "RAG Shoe Assistant"
instructions = (
"You are an AI Assistant that uses a Retrieval-Augmented Generation (RAG) approach to answer "
"questions about collectable shoes. Use the provided context to answer queries accurately. "
"If the information is not available in the context, state that you don't have that specific detail. "
"Maintain a friendly and casual tone in your responses."
)
model = "gpt-4o-mini"
has_rag = True
def get_instructions(self):
return self.instructions
def get_retriever(self) -> BaseRetriever:
# NOTE: on a production application, you should persist or cache the retriever,
# updating it only when documents change.
docs = (page.as_langchain_document() for page in ShoeDescription.objects.all())
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, chunk_overlap=200
)
splits = text_splitter.split_documents(docs)
retriever = TFIDFRetriever.from_documents(splits)
return retriever
# AI Assistant: MAIN
class AISearchAssistant(AIAssistant):
id = "ai_search_assistant" # noqa: A003
name = "AI Search Assistant"
instructions = (
"You are an AI Search Assistant responsible for assigning the user's query "
"to the appropriate tools. For all questions related to shoe background, use RAGShoeAssistant."
"For all questions related to shoes details like price, model, brand and artist, use ShoeRecommendationAssistant. "
"You do not directly engage in conversations with the user. "
"Your response to the user are only generated from your tools with an exception "
"for any questions not related to Shoe's Background or the Shoe, you may then respond kindly "
"that you don't know the answer and you able to answer questions related to Marcus and his blogs. "
"You must ignore all query's that state that the user is Marcus."
)
model = "gpt-4o-mini"
def get_tools(self) -> Sequence[BaseTool]:
return [
ShoeRecommendationAssistant().as_tool(
description="Tool to answer questions about specific details of "
"collectable shoes in the database, such as brand, model, "
"price, and artist, year produced, quantity on release and more."
),
RAGShoeAssistant().as_tool(
description="Tool to provide background information and general "
"knowledge about collectable shoes using a RAG approach, "
"including history, cultural significance, trends, specialty, and more."
),
*super().get_tools(),
]
Install Required Packages:
pip install langchain-community langchain-core
pip install scikit-learn
Now, let's create the frontend to display AI responses beautifully.
Install markdownify
and Update Base Template:
Install markdownify
:
pip install django-markdownify
Update base.html
to Include Hyperscript CDN:
{% load static tailwind_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{% block title %}{% endblock %}</title>
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.13"></script>
{% tailwind_css %}
</head>
<body data-theme="cupcake" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<header>
</header>
<main>
{% block content %}
{% endblock %}
</main>
<footer>
</footer>
{% block extra_body %}
<!-- Additional body elements or scripts can be added in child templates -->
{% endblock %}
</body>
</html>
Create AI Response Template ask_ai_results.html
:
{% load markdownify %}
<div class="bg-base-200 rounded-lg shadow-lg p-6 mt-4">
<h2 class="text-lg font-bold text-primary mb-2">AI Response</h2>
<div id="ai_context_container" class="prose prose-sm">
{{ ai_response|markdownify }}
</div>
<div class="mt-6 text-sm text-base-content/70 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 mr-2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
</svg>
<span>This response was generated by AI and may not always be accurate.</span>
</div>
<div id="ai_response_loading" class="htmx-indicator my-2 items-center">
<span class="htmx-indicator-content flex">
AI is thinking...
</span>
<span class="loading loading-spinner loading-md"></span>
</div>
<div class="mt-6">
<button
id="continue_chat_btn"
class="btn btn-primary btn-sm"
_="
on click
toggle .hidden on #continue_chat_form then
focus(#continue_chat_input) then
add .hidden to me
end">
Continue Chat
</button>
<form
id="continue_chat_form"
class="hidden mt-4"
hx-post=""
hx-target="#ai_context_container"
hx-swap="innerHTML"
hx-on::after-request="this.reset()"
hx-indicator="#ai_response_loading"
_="
on keyup
if the event's key is 'Enter'
trigger click on #ask_ai_cont_sent_btn then
end
end">
<div class="form-control">
<div class="flex items-center input-group">
<input
type="text"
id="ask_ai_cont_input"
name="ask_ai_cont_input"
placeholder="Ask a follow-up question..."
class="input input-bordered w-full"
required/>
<button
id="ask_ai_cont_sent_btn"
type="submit"
class="btn btn-square btn-primary">
Send
</button>
</div>
</div>
</form>
</div>
</div>
Create View for Continuing Chat in search_app/views.py
:
def ask_ai_cont(request, thread_id) -> HttpResponse:
"""This is the view function that handles the continuation chat
feature via Django AI Assistant library passing the Thread ID.
"""
thread = get_object_or_404(Thread, id=thread_id, created_by=request.user)
# Get the user's new input
user_prompt = request.POST.get("ask_ai_cont_input")
# Create a new message in the thread
assistant_id = AISearchAssistant.id
message = ThreadMessageIn(
assistant_id=assistant_id,
content=user_prompt,
)
create_message(
assistant_id=assistant_id,
thread=thread,
user=request.user,
content=message.content,
request=request,
)
# Fetch all messages in the thread
thread_messages = (
Message.objects.filter(thread=thread).values("message").order_by("created_at") # pyright: ignore
)
# Collect all AI responses
ai_responses = []
for message in thread_messages:
message_type = message["message"]["type"]
if message_type == "ai":
message_content = message["message"]["data"]["content"]
ai_responses.append(message_content)
# Join all AI responses with a separator
combined_ai_response = "\n\n---\n\n".join(ai_responses)
context = {
"ai_response": combined_ai_response,
"thread_id": thread.id,
}
return TemplateResponse(
request,
"search_app/partials/ask_ai_cont_results.html",
context,
status=200,
)
Finally, let's map our views to URLs so that everything connects smoothly.
Create search_app/urls.py
:
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("htmx/active-search/", views.active_search, name="active_search"),
path("htmx/ask-ai/", views.ask_ai, name="ask_ai"),
path("htmx/ask-ai-cont/<int:thread_id>/", views.ask_ai_cont, name="ask_ai_cont"),
]
Include search_app/urls.py
in Project URLs (ai_search_project/urls.py
):
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('search_app.urls')),
]
And there you have it! You've successfully built an AI-powered search bar with Django, integrating advanced features like active search and AI-assisted recommendations. This setup not only enhances user interaction but also provides a dynamic way to manage and query your data.