How I Developed Hue's AI Art Tool with MindsDB & OpenAI's Dall-E Image Generation Model
A few months ago, Hue was a simple online art store where users could create an account to purchase and download artwork. Now, Hue is a sophisticated AI art & image generation tool where users can create, license, and even sell their awesome creations!
You probably think it took a lot of time and complexity to enhance my program with artificial intelligence. You may even think this is too complicated for you. But the truth is, I was able to add this functionality rather quickly and easily thanks to MindsDB's Python SDK... and so can you! Follow along to see how I did it.
Training Custom Models
To begin with, I headed over to https://cloud.mindsdb.com/ and trained custom models specific to each genre I selected.
Create Model
Street Art Model:
CREATE MODEL open_ai.street_art
PREDICT img_url
USING
engine = 'openai',
mode = 'image',
prompt_template = '{{text}}, 4K | highly detailed, contemporary, urban street art style of Kaws combined with the controversial style of Banksy | bright lighting | warrm happy colors ';
I ran this query to confirm the model was done training.
Checking the Status of The Model
SELECT status
FROM open_ai.models
WHERE name='street_art';
Then, I ran a few test queries from MindsDB Cloud to see how accurate the model was based on the specified parameters.
Query The Model
SELECT *
FROM open_ai.street_art
WHERE text='A statue of a cool mouse with headphones, baggy pants, and a t-shirt on a side-street in NYC.'
Python/Django Integration
Next, I headed over to my Django/Python project and used this command to create a new app (named Hugo). (Because this is an AI, I gave it a clever name that is a play on 'Hue'):
python manage.py startapp hugo
Once that was ready, I created custom database models to support my new integration:
hugo/models.py
class Style(models.Model):
name = models.CharField(max_length=254)
friendly_name = models.CharField(max_length=254, null=True, blank=True)
class Meta:
verbose_name_plural = 'Styles'
def __str__(self):
return self.name
def get_friendly_name(self):
return self.friendly_name
class Artwork(models.Model):
id = models.AutoField(primary_key=True)
artwork_description = models.TextField(null=True, blank=True)
title = models.CharField(max_length=254, unique=True)
image_url = models.URLField(max_length=1024, null=True, blank=True)
image = models.ImageField(null=True, blank=True, )
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
deleted_at = models.DateTimeField(auto_now=True)
# Use a foreign key to give each user ownership of their artwork
user = models.ForeignKey(User, related_name='users', on_delete=models.CASCADE, blank=True, null=True)
style = models.ForeignKey('Style', null=True, blank=True,on_delete=models.SET_NULL)
# Boolean field so user can choose if artwork public
is_public = models.BooleanField(default=False)
# Boolean field so user can decide if artwork is downloadable
is_downloadable = models.BooleanField(default=False)
# Allows users to import artwork to store
for_sale = models.BooleanField(default=False)
# Allows users to set a price for artwork imported to store
price = models.DecimalField(
decimal_places=2, max_digits=8, null=False, default=0)
in_import_queue = models.BooleanField(default=False)
def get_style(l):
return dict(Style.objects.filter).get(l)
class Meta:
verbose_name_plural = 'artworks'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
def __str__(self):
return self.title
And some views to render & sort the custom artwork on the front end:
hugo/views.py
def hugo(request):
artworks = Artwork.objects.all()
styles = None
# sort artwork by style if the user has selected that style
if request.GET:
if 'style' in request.GET:
styles = request.GET['style'].split(',')
artworks = artworks.filter(style__name__in=styles)
styles = Style.objects.filter(name__in=styles)
# display artwork from newest to oldest whe visiting the page
artworks = artworks.order_by('-created_at')
context = {
'artworks': artworks,
'current_styles': styles,
# 'sort_dir': sort_dir,
}
return render(request, 'hugo.html', context)
I then configured the urls for the hugo app and the entire project.
hugo/views/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.artwork, name='artwork'),
path('<artwork_id>', views.artwork_detail, name='artwork_detail'),
path('add_artwork/', views.add_artwork, name='add_artwork'),
]
hue/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls import url, handler404
from home.views import page_not_found_view
handler404 = page_not_found_view
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),
url(r'^accounts/', include('allauth.urls')),
path('', include('home.urls')),
path('page_not_found_view/', page_not_found_view, name='page_not_found_view'),
path('shop/', include('shop.urls')),
path('cart/', include('cart.urls')),
path('checkout/', include('checkout.urls')),
path('profile/', include('profiles.urls')),
# New AI app is defined here
path('hugo/', include('hugo.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
static('django.views.static', (r'media/(?P<path>.*)', 'serve', {'document_root': settings.MEDIA_ROOT}),
)
Create Artwork
To get started, I created a form using fields from the Artwork
model I created above:
from django import forms
from .models import Artwork
class ArtworkForm(forms.ModelForm):
class Meta:
model = Artwork
fields = ['title', 'style', 'artwork_description', 'is_downloadable', 'is_public']
def __init__(self, *args, **kwargs):
"""
Add placeholders and classes, remove auto-generated
labels and set autofocus on first field
"""
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
""" Set a length requirement for the artwork description """
if field_name == 'artwork_description':
field.widget.attrs['maxlength'] = 500
field.widget.attrs['rows'] = 3
field.widget.attrs['placeholder'] = 'Describe your artwork'
field.widget.attrs['class'] = 'border-1 rounded shadow-sm'
field.widget.attrs['required'] = True
if field_name == 'name':
field.widget.attrs['placeholder'] = 'Give your artwork a name'
field.widget.attrs['class'] = 'border-1 rounded shadow-sm'
field.widget.attrs['required'] = True
else:
field.widget.attrs['class'] = 'border-1 rounded shadow-sm'
field.widget.attrs['required'] = True
Then, I added a new view for rendering the Add Artwork
form on the front end. As you will see below, the artwork generated by these models is returned as an image_url (a clickable link containing the image). Then, the 'add_hugo' method in hugo/views.py automates the process of downloading the image as a .jpg
and assigns it to the artwork model.
hugo/views.py
@login_required
def add_hugo(request):
if request.method == 'POST':
form = ArtworkForm(request.POST, request.FILES)
try:
if form.is_valid():
artwork = form.save(commit=False)
artwork.user = request.user
artwork.save()
style = artwork.style
text = artwork.artwork_description
encoded_text = quote(text)
# Define a dictionary to map style names to query templates
style_queries = {
'pop-art': 'SELECT * FROM open_ai.retro WHERE text="{}";',
'digital-art': 'SELECT * FROM open_ai.digital_only WHERE text="{}";',
'fine-art': 'SELECT * FROM open_ai.fine_art WHERE text="{}";',
'street-art': 'SELECT * FROM open_ai.urban_art WHERE text="{}";',
'abstract-art': 'SELECT * FROM open_ai.abstract WHERE text="{}";',
'photography': 'SELECT * FROM open_ai.photography WHERE text="{}";'
}
query_template = style_queries.get(style.name)
if query_template is not None:
query = query_template.format(encoded_text) # Insert encoded_text into the query template
mdb_server = mindsdb_sdk.connect('https://cloud.mindsdb.com', settings.MINDSDB_EMAIL, settings.MINDSDB_PASSWORD)
project = mdb_server.get_project('open_ai')
query_result = project.query(query).fetch()
ai_img = DataFrame.to_string(query_result)
# Extract the image URL from ai_img using regular expressions
url_pattern = r'(https?://\S+)'
match = re.search(url_pattern, ai_img)
if match:
image_url = match.group(1)
else:
# Handle the case where no valid URL is found
image_url = None
else:
# Handle the case where the selected style is not recognized
image_url = None
if image_url:
# Download the image from the URL
response = requests.get(image_url)
if response.status_code == 200:
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(response.content)
img_temp.flush()
# Generate a random name for the image file
timestamp = str(int(time.time())) # Get the current timestamp
random_str = ''.join(random.choices(string.ascii_lowercase, k=6)) # Generate a random string of length 6
image_filename = f'image_{timestamp}_{random_str}.jpg'
# Assign the downloaded image to the artwork model with the arandom name
artwork.image_url = image_url
artwork.image.save(image_filename, files.File(img_temp))
artwork.save() # Save the artwork model again to update the image field
return redirect(reverse('hugo'))
except Exception as e:
# Delete the artwork if an exception occurs
artwork.delete()
return render(request, 'runtime_error_template.html', {'error_message': str(e)})
else:
form = ArtworkForm()
template = 'add_hugo.html'
context = {'form': form}
return render(request, template, context)
Next, I added the HTML form on the front end:
(Note: I used JavaScript to create a random prompt generator and add some defensive programming to prevent any errors from MindsDB and/or OpenAI)
<form method="POST" action="{% url 'add_hugo' %}" class="form mb-2" enctype="multipart/form-data"
onsubmit="return showLoading() && validateForm()">
{% csrf_token %}
{% for field in form %}
{% if field.name == 'artwork_description' %}
{{ field | as_crispy_field }}
<div class="text-start mt-3">
<a type="button" class="body-font random-prompt" onclick="generateRandomPrompt()">Surprise
Me!</a>
</div>
<div id="charCount" class="text-end mt-1">
<small>Character count: <span id="charCountValue">0</span>/400</small>
</div>
{% else %}
{{ field | as_crispy_field }}
{% endif %}
{% endfor %}
<div class="text-right">
<a class="btn btn-dark rounded-0" href="{% url 'hugo' %}">Cancel</a>
</button>
<button id="createArtBtn" class="btn btn-dark rounded-0" type="submit">Create Art</button>
</div>
</form>
{% endblock %}
{% block postloadjs %}
{{ block.super }}
# random prompt generator
<script>
function generateRandomPrompt() {
var prompts = [
"A surreal landscape with floating islands",
"The Hulk, wearing an apron and baking a cake. Retro, 1950's aesthetic",
"A futuristic cityscape at sunset",
"...."
];
var randomPrompt = prompts[Math.floor(Math.random() * prompts.length)];
document.getElementById('id_artwork_description').value = randomPrompt;
updateCharCount(randomPrompt); // Update character count after setting the prompt
}
function validateForm() {
var descriptionField = document.getElementById('id_artwork_description');
var descriptionValue = descriptionField.value.toLowerCase();
var charCount = descriptionValue.length;
if (charCount > 400) {
alert('Description is too long. Please keep it under 400 characters.');
return false; // Prevent form submission if character count exceeds 400
}
return true; // Proceed with form submission if validation passes
}
document.getElementById('id_artwork_description').addEventListener('input', function () {
var charCount = this.value.length;
document.getElementById('charCountValue').textContent = charCount;
});
function updateCharCount(text) {
var charCount = text.length;
document.getElementById('charCountValue').textContent = charCount;
}
</script>
I even added functionality to handle runtime errors and prevent the creation of database rows with empty or invalid fields.
The images created by users are saved to the media
folder, which was configured in the Django project's main settings.py
file:
# Media
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIAFILES_LOCATION = 'media'
# Override static and media URLs in production
STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{STATICFILES_LOCATION}/'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{MEDIAFILES_LOCATION}/'
Note: AWS_S3_CUSTOM_DOMAIN and other sensitive information are stored securely in the project's environment.
With all this in place, users can fill out the Add Artwork
form on the front end it will send their artwork description to MindsDB as the {text}
prompt in the query, SELECT * FROM open_ai.query_name WHERE text="{text}";
and return the user's AI image.
Resources
Hue
Link to Live Project: https://hue-alissa.herokuapp.com/
Source Code: https://github.com/alissatroiano/hue
MindsDB:
MindsDB Python SDK
MindsDB's OpenAI Dall-E integration:
OpenAI
Dall-E