Complete Full-Stack AI Persona Generator: Django REST API + React Frontend with Ollama Integration
Comprehensive tutorial for building a sophisticated full-stack application using Django backend with REST API, React TypeScript frontend, and Ollama LLM integration for AI-powered persona-based content generation.
Daniel Kliewer
Author, Sovereign AI


Building a Full-Stack Application with Django and React: A Step-by-Step Guide
In this comprehensive guide, we'll walk through the process of building a full-stack application using Django for the backend and React for the frontend. The application allows users to upload a writing sample, analyzes it using an AI language model, and generates blog posts in the style of the uploaded sample.
GitHub Repository: kliewerdaniel/Django-React-Ollama-Integration
Introduction
This guide aims to help you build a full-stack application that:
-
Backend (Django):
- Allows users to upload a writing sample.
- Analyzes the writing sample using an AI language model.
- Stores the analysis and allows generating new content based on the analysis.
-
Frontend (React):
- Provides a user interface to upload writing samples.
- Displays a list of saved personas (analysis results).
- Allows generating and viewing blog posts in the style of the uploaded samples.
Setting Up the Backend with Django
Creating a Django Project
First, ensure you have Python and Django installed. Create a new Django project and application:
bash1django-admin startproject backend2cd backend3python manage.py startapp core
Configuring Settings
Update the backend/settings.py file to include the necessary configurations:
- Add
rest_framework,core, andcorsheaderstoINSTALLED_APPS. - Configure middleware to include
CorsMiddleware. - Set up
CORS_ALLOWED_ORIGINSto allow your frontend to communicate with the backend.
python1# backend/settings.py23INSTALLED_APPS = [4 # ...5 'rest_framework',6 'core',7 'corsheaders',8]910MIDDLEWARE = [11 'corsheaders.middleware.CorsMiddleware',12 # ...13]1415CORS_ALLOWED_ORIGINS = [16 'http://localhost:3000', # Frontend URL17]
Defining Models
Create models for Persona and BlogPost in core/models.py:
python1# core/models.py23from django.db import models45class Persona(models.Model):6 name = models.CharField(max_length=100)7 data = models.JSONField()89 def __str__(self):10 return self.name1112class BlogPost(models.Model):13 persona = models.ForeignKey(Persona, on_delete=models.CASCADE, related_name='blog_posts')14 title = models.CharField(max_length=200, blank=True, null=True)15 content = models.TextField()16 created_at = models.DateTimeField(auto_now_add=True)1718 def __str__(self):19 return self.title or f"BlogPost {self.id}"
Apply the migrations:
bash1python manage.py makemigrations2python manage.py migrate
Creating Serializers
Define serializers to convert model instances to JSON and vice versa in core/serializers.py:
python1# core/serializers.py23from rest_framework import serializers4from .models import Persona, BlogPost5from .utils import analyze_writing_sample6import logging78logger = logging.getLogger(__name__)910class PersonaSerializer(serializers.ModelSerializer):11 writing_sample = serializers.CharField(write_only=True)1213 class Meta:14 model = Persona15 fields = ['id', 'name', 'writing_sample', 'data']16 read_only_fields = ['id', 'data']1718 def create(self, validated_data):19 writing_sample = validated_data.pop('writing_sample')20 logger.debug(f"Writing sample received: {writing_sample[:100]}...")21 analyzed_data = analyze_writing_sample(writing_sample)22 logger.debug(f"Analyzed data: {analyzed_data}")23 if not analyzed_data:24 logger.error("Failed to analyze the writing sample.")25 raise serializers.ValidationError({"writing_sample": "Analysis failed."})26 validated_data['data'] = analyzed_data27 return Persona.objects.create(**validated_data)2829class BlogPostSerializer(serializers.ModelSerializer):30 persona = serializers.StringRelatedField()3132 class Meta:33 model = BlogPost34 fields = ['id', 'persona', 'title', 'content', 'created_at']
Writing Utility Functions
Create utility functions in core/utils.py to interact with the AI language model and process responses:
python1# core/utils.py23import logging4import requests5import json6import re7from decouple import config89logger = logging.getLogger(__name__)10OLLAMA_API_URL = config('OLLAMA_API_URL', default='http://localhost:11434/api/generate')1112def extract_json(response_text):13 decoder = json.JSONDecoder()14 pos = 015 while pos < len(response_text):16 try:17 obj, pos = decoder.raw_decode(response_text, pos)18 return obj19 except json.JSONDecodeError:20 pos += 121 return None2223def analyze_writing_sample(writing_sample):24 encoding_prompt = f'''25Please analyze the writing style and personality of the given writing sample. Provide a detailed assessment of their characteristics using the following template. Rate each applicable characteristic on a scale of 1-10 where relevant, or provide a descriptive value. Return the results in a JSON format.262728 "name": "[Author/Character Name]",29 "vocabulary_complexity": [1-10],30 "sentence_structure": "[simple/complex/varied]",31 "paragraph_organization": "[structured/loose/stream-of-consciousness]",32 "idiom_usage": [1-10],33 "metaphor_frequency": [1-10],34 "simile_frequency": [1-10],35 "tone": "[formal/informal/academic/conversational/etc.]",36 "punctuation_style": "[minimal/heavy/unconventional]",37 "contraction_usage": [1-10],38 "pronoun_preference": "[first-person/third-person/etc.]",39 "passive_voice_frequency": [1-10],40 "rhetorical_question_usage": [1-10],41 "list_usage_tendency": [1-10],42 "personal_anecdote_inclusion": [1-10],43 "pop_culture_reference_frequency": [1-10],44 "technical_jargon_usage": [1-10],45 "parenthetical_aside_frequency": [1-10],46 "humor_sarcasm_usage": [1-10],47 "emotional_expressiveness": [1-10],48 "emphatic_device_usage": [1-10],49 "quotation_frequency": [1-10],50 "analogy_usage": [1-10],51 "sensory_detail_inclusion": [1-10],52 "onomatopoeia_usage": [1-10],53 "alliteration_frequency": [1-10],54 "word_length_preference": "[short/long/varied]",55 "foreign_phrase_usage": [1-10],56 "rhetorical_device_usage": [1-10],57 "statistical_data_usage": [1-10],58 "personal_opinion_inclusion": [1-10],59 "transition_usage": [1-10],60 "reader_question_frequency": [1-10],61 "imperative_sentence_usage": [1-10],62 "dialogue_inclusion": [1-10],63 "regional_dialect_usage": [1-10],64 "hedging_language_frequency": [1-10],65 "language_abstraction": "[concrete/abstract/mixed]",66 "personal_belief_inclusion": [1-10],67 "repetition_usage": [1-10],68 "subordinate_clause_frequency": [1-10],69 "verb_type_preference": "[active/stative/mixed]",70 "sensory_imagery_usage": [1-10],71 "symbolism_usage": [1-10],72 "digression_frequency": [1-10],73 "formality_level": [1-10],74 "reflection_inclusion": [1-10],75 "irony_usage": [1-10],76 "neologism_frequency": [1-10],77 "ellipsis_usage": [1-10],78 "cultural_reference_inclusion": [1-10],79 "stream_of_consciousness_usage": [1-10],80 "openness_to_experience": [1-10],81 "conscientiousness": [1-10],82 "extraversion": [1-10],83 "agreeableness": [1-10],84 "emotional_stability": [1-10],85 "dominant_motivations": "[achievement/affiliation/power/etc.]",86 "core_values": "[integrity/freedom/knowledge/etc.]",87 "decision_making_style": "[analytical/intuitive/spontaneous/etc.]",88 "empathy_level": [1-10],89 "self_confidence": [1-10],90 "risk_taking_tendency": [1-10],91 "idealism_vs_realism": "[idealistic/realistic/mixed]",92 "conflict_resolution_style": "[assertive/collaborative/avoidant/etc.]",93"relationship_orientation": "[independent/communal/mixed]",94"emotional_response_tendency": "[calm/reactive/intense]",95"creativity_level": [1-10],96"age": "[age or age range]",97 "gender": "[gender]",98 "education_level": "[highest level of education]",99 "professional_background": "[brief description]",100 "cultural_background": "[brief description]",101 "primary_language": "[language]",102 "language_fluency": "[native/fluent/intermediate/beginner]",103 "background": "[A brief paragraph describing the author's context, major influences, and any other relevant information not captured above]"104105106Writing Sample:107{writing_sample}108'''109110 payload = {111 'model': 'llama3.2', # Replace with your Ollama model name112 'prompt': encoding_prompt,113 'stream': False114 }115 headers = {'Content-Type': 'application/json'}116117 try:118 response = requests.post(OLLAMA_API_URL, json=payload, headers=headers)119 response.raise_for_status()120 json_str = re.search(r'\{.*?\}', response.text, re.DOTALL).group()121 analyzed_data = extract_json(response.text)122 if analyzed_data is None:123 logger.error("No JSON object found in the response.")124 return None125 return analyzed_data126 except (requests.RequestException, json.JSONDecodeError, AttributeError) as e:127 logger.error(f"Error during analyze_writing_sample: {str(e)}")128 return None129130def generate_content(persona_data, prompt):131 decoding_prompt = f'''132You are to write a blog post in the style of {persona_data.get('name', 'Unknown Author')}, a writer with the following characteristics:133134{json.dumps(persona_data, indent=2)}135136Now, please write a response in this style about the following topic:137"{prompt}"138Begin with a compelling title that reflects the content of the post.139'''140141 payload = {142 'model': 'llama3.2', # Replace with your Ollama model name143 'prompt': decoding_prompt,144 'stream': False145 }146 headers = {'Content-Type': 'application/json'}147148 try:149 logger.info(f"Sending request to OLLAMA API at {OLLAMA_API_URL} with payload: {payload}")150 response = requests.post(OLLAMA_API_URL, json=payload, headers=headers)151 logger.info(f"Received response from OLLAMA API: Status Code {response.status_code}")152153 response.raise_for_status()154155 response_json = response.json()156 response_content = response_json.get('response', '').strip()157 if not response_content:158 logger.error("OLLAMA API response 'response' field is empty.")159 return ''160161 return response_content162163 except requests.RequestException as e:164 logger.error(f"Error during generate_content: {e}")165 if hasattr(e, 'response') and e.response:166 logger.error(f"Ollama Response Status: {e.response.status_code}")167 logger.error(f"Ollama Response Body: {e.response.text}")168 return ''169170def save_blog_post(blog_post, title):171 # Implement if needed172 pass
Building Views
Create views to handle API requests in core/views.py:
python1from django.shortcuts import render23# Create your views here.4import logging5from rest_framework.views import APIView6from rest_framework.response import Response7from rest_framework import status, generics8from .serializers import PersonaSerializer, BlogPostSerializer9from .models import Persona, BlogPost10from .utils import generate_content1112logger = logging.getLogger(__name__)1314class AnalyzeWritingSampleView(APIView):15 def post(self, request, *args, **kwargs):16 logger.debug(f"Request data: {request.data}")17 serializer = PersonaSerializer(data=request.data)18 if serializer.is_valid():19 persona = serializer.save()20 return Response(PersonaSerializer(persona).data, status=status.HTTP_201_CREATED)21 else:22 logger.error(f"Serializer validation failed: {serializer.errors}")23 return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)2425class GenerateContentView(APIView):26 def post(self, request):27 persona_id = request.data.get('persona_id')28 prompt = request.data.get('prompt')2930 if not persona_id:31 logger.warning('persona_id is required.')32 return Response({'error': 'persona_id is required.'}, status=status.HTTP_400_BAD_REQUEST)3334 if not prompt:35 logger.warning('prompt is required.')36 return Response({'error': 'prompt is required.'}, status=status.HTTP_400_BAD_REQUEST)3738 try:39 persona = Persona.objects.get(id=persona_id)40 except Persona.DoesNotExist:41 logger.warning(f"Persona with ID {persona_id} not found.")42 return Response({'error': f'Persona with ID {persona_id} not found'}, status=status.HTTP_404_NOT_FOUND)4344 blog_post_content = generate_content(persona.data, prompt)4546 if not blog_post_content:47 logger.error('Failed to generate blog post.')48 return Response({'error': 'Failed to generate blog post.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)4950 # Create BlogPost object51 lines = blog_post_content.strip().split('\n')52 title = lines[0] if lines else 'Untitled'53 content = '\n'.join(lines[1:]) if len(lines) > 1 else ''5455 blog_post = BlogPost.objects.create(56 persona=persona,57 title=title,58 content=content59 )6061 return Response(BlogPostSerializer(blog_post).data, status=status.HTTP_201_CREATED)6263class PersonaListView(generics.ListAPIView):64 queryset = Persona.objects.all()65 serializer_class = PersonaSerializer6667class PersonaDetailView(APIView):68 def get(self, request, persona_id):69 try:70 persona = Persona.objects.get(id=persona_id)71 except Persona.DoesNotExist:72 logger.warning(f"Persona with ID {persona_id} not found.")73 return Response({'error': 'Persona not found'}, status=status.HTTP_404_NOT_FOUND)7475 serializer = PersonaSerializer(persona)76 return Response(serializer.data, status=status.HTTP_200_OK)7778class BlogPostView(generics.ListAPIView):79 queryset = BlogPost.objects.all().order_by('-created_at')80 serializer_class = BlogPostSerializer
Setting Up URLs
Define API endpoints in core/urls.py:
python1# core/urls.py23from django.urls import path4from .views import (5 AnalyzeWritingSampleView,6 GenerateContentView,7 PersonaListView,8 PersonaDetailView,9 BlogPostView10)1112urlpatterns = [13 path('analyze/', AnalyzeWritingSampleView.as_view(), name='analyze-writing-sample'),14 path('generate/', GenerateContentView.as_view(), name='generate-content'),15 path('personas/', PersonaListView.as_view(), name='persona-list'),16 path('personas/<int:persona_id>/', PersonaDetailView.as_view(), name='persona-detail'),17 path('blog-posts/', BlogPostView.as_view(), name='blog-posts'),18]
Include the core app's URLs in the project's urls.py:
python1# backend/urls.py23from django.contrib import admin4from django.urls import path, include56urlpatterns = [7 path('admin/', admin.site.urls),8 path('api/', include('core.urls')),9]
Setting Up the Frontend with React
Creating a React App
Ensure you have Node.js and npm installed. Create a new React application:
bash1npx create-react-app frontend --template typescript2cd frontend
Update package.json to include necessary dependencies:
json1// frontend/package.json23{4 "name": "frontend",5 "version": "0.1.0",6 "private": true,7 "dependencies": {8 // ...9 "axios": "^1.7.7",10 "react-router-dom": "^6.27.0"11 },12 // ...13}
Install the new dependencies:
bash1npm install
Configuring Axios
Create an Axios instance for consistent API calls in src/axiosConfig.ts:
typescript1// src/axiosConfig.ts23import axios from 'axios';45const instance = axios.create({6 baseURL: 'http://localhost:8000/api/', // Backend URL7});89export default instance;
Building Components
Create the following components:
UploadSample Component
Allows users to upload a writing sample.
typescript1import React, { useState } from 'react';2import axios from '../axiosConfig'; // Adjust the path if necessary34const UploadSample: React.FC = () => {5 const [name, setName] = useState('');6 const [writingSample, setWritingSample] = useState('');7 const [error, setError] = useState<string | null>(null);8 const [success, setSuccess] = useState<string | null>(null);910 const handleSubmit = async (event: React.FormEvent) => {11 event.preventDefault();1213 const payload = {14 name: name.trim(),15 writing_sample: writingSample.trim(),16 };1718 try {19 console.log('Payload being sent:', payload);20 const response = await axios.post('analyze/', payload);21 console.log('Response received:', response.data);22 setSuccess(`Persona "${response.data.name}" created successfully!`);23 setError(null);24 setName('');25 setWritingSample('');26 } catch (error: any) {27 console.error('Error uploading writing sample:', error);28 console.log('Error response:', error.response);29 if (error.response && error.response.data) {30 setError(JSON.stringify(error.response.data));31 } else {32 setError('An error occurred while uploading the writing sample.');33 }34 setSuccess(null);35 }36 };3738 return (39 <div>40 <h2>Upload Writing Sample</h2>41 {error && <div style={{ color: 'red' }}>Error: {error}</div>}42 {success && <div style={{ color: 'green' }}>{success}</div>}43 <form onSubmit={handleSubmit}>44 <div>45 <label htmlFor="name">Persona Name:</label>46 <input47 type="text"48 id="name"49 value={name}50 onChange={(e) => setName(e.target.value)}51 required52 maxLength={100}53 />54 </div>55 <div>56 <label htmlFor="writingSample">Writing Sample:</label>57 <textarea58 id="writingSample"59 value={writingSample}60 onChange={(e) => setWritingSample(e.target.value)}61 required62 rows={10}63 cols={50}64 ></textarea>65 </div>66 <button type="submit">Submit</button>67 </form>68 </div>69 );70};7172export default UploadSample;
PersonaList Component
Displays a list of saved personas.
typescript1// src/components/PersonaList.tsx23import React, { useEffect, useState } from 'react';4import axios from '../axiosConfig';5import { useNavigate } from 'react-router-dom';67const PersonaList: React.FC = () => {8 import React, { useEffect, useState } from 'react';9import axios from '../axiosConfig'; // Adjust the path if necessary10import { useNavigate } from 'react-router-dom';1112interface Persona {13 id: number;14 name: string;15 data: Record<string, any>;16}1718const PersonaList: React.FC = () => {19 const [personas, setPersonas] = useState<Persona[]>([]);20 const [loading, setLoading] = useState<boolean>(true);21 const [error, setError] = useState<string | null>(null);22 const navigate = useNavigate();2324 useEffect(() => {25 const fetchPersonas = async () => {26 try {27 const response = await axios.get('personas/');28 setPersonas(response.data);29 } catch (err) {30 console.error('Error fetching personas:', err);31 setError('Failed to load personas.');32 } finally {33 setLoading(false);34 }35 };3637 fetchPersonas();38 }, []);3940 const handleSelectPersona = (personaId: number) => {41 navigate(`/generate?personaId=${personaId}`);42 };4344 if (loading) return <div className="loading">Loading...</div>;45 if (error) return <div className="error">{error}</div>;4647 return (48 <div>49 <h2>Saved Personas</h2>50 {personas.length === 0 ? (51 <p>No personas found.</p>52 ) : (53 <ul>54 {personas.map((persona) => (55 <li key={persona.id}>56 {persona.name}57 <button onClick={() => handleSelectPersona(persona.id)}>58 Generate Content59 </button>60 </li>61 ))}62 </ul>63 )}64 </div>65 );66};6768export default PersonaList;
GenerateContent Component
Allows generating content based on a selected persona.
typescript1// src/components/GenerateContent.tsx23import React, { useState } from 'react';4import axios from '../axiosConfig';5import { useSearchParams } from 'react-router-dom';67const GenerateContent: React.FC = () => {8import React, { useState } from 'react';9import axios from '../axiosConfig'; // Adjust the path if necessary10import { useSearchParams } from 'react-router-dom';1112interface BlogPost {13 id: number;14 persona: string;15 title: string;16 content: string;17 created_at: string;18}1920const GenerateContent: React.FC = () => {21 const [searchParams] = useSearchParams();22 const personaIdParam = searchParams.get('personaId');23 const personaId = personaIdParam ? Number(personaIdParam) : null;24 const [prompt, setPrompt] = useState<string>('');25 const [content, setContent] = useState<BlogPost | null>(null);26 const [loading, setLoading] = useState<boolean>(false);27 const [error, setError] = useState<string | null>(null);2829 const handleGenerate = async () => {30 if (!prompt) {31 setError('Please enter a prompt.');32 return;33 }34 if (!personaId) {35 setError('Invalid Persona ID.');36 return;37 }38 setLoading(true);39 setError(null);4041 try {42 const response = await axios.post('generate/', {43 persona_id: personaId,44 prompt: prompt,45 });46 setContent(response.data);47 setError(null);48 setPrompt('');49 } catch (err: any) {50 console.error('Error generating content:', err);51 if (err.response && err.response.data) {52 setError(JSON.stringify(err.response.data));53 } else {54 setError('Failed to generate content.');55 }56 } finally {57 setLoading(false);58 }59 };6061 return (62 <div>63 <h2>Generate Content</h2>64 <div>65 <label htmlFor="prompt">Prompt:</label>66 <textarea67 id="prompt"68 value={prompt}69 onChange={(e) => setPrompt(e.target.value)}70 placeholder="Enter a topic or prompt..."71 rows={4}72 cols={50}73 required74 />75 </div>76 <button onClick={handleGenerate} disabled={loading}>77 {loading ? 'Generating...' : 'Generate Content'}78 </button>79 {error && <p className="error">Error: {error}</p>}80 {content && (81 <div>82 <h3>{content.title}</h3>83 <p>{content.content}</p>84 </div>85 )}86 </div>87 );88};8990export default GenerateContent;
BlogPosts Component
Displays generated blog posts.
typescript1import React, { useEffect, useState } from 'react';2import axios from '../axiosConfig'; // Adjust the path if necessary3interface BlogPost {4 id: number;5 persona: string;6 title: string;7 content: string;8 created_at: string;9}1011const BlogPosts: React.FC = () => {12 const [blogPosts, setBlogPosts] = useState<BlogPost[]>([]);13 const [loading, setLoading] = useState<boolean>(true);14 const [error, setError] = useState<string | null>(null);1516 useEffect(() => {17 const fetchBlogPosts = async () => {18 try {19 const response = await axios.get('blog-posts/');20 setBlogPosts(response.data);21 } catch (err) {22 console.error('Error fetching blog posts:', err);23 setError('Failed to load blog posts.');24 } finally {25 setLoading(false);26 }27 };2829 fetchBlogPosts();30 }, []);3132 if (loading) return <p>Loading...</p>;33 if (error) return <p className="error">{error}</p>;3435 return (36 <div>37 <h2>Blog Posts</h2>38 {blogPosts.length === 0 ? (39 <p>No blog posts found.</p>40 ) : (41 <ul>42 {blogPosts.map((post) => (43 <li key={post.id}>44 <h3>{post.title || 'Untitled'}</h3>45 <p>{post.content}</p>46 <small>47 By: {post.persona} on{' '}48 {new Date(post.created_at).toLocaleString()}49 </small>50 </li>51 ))}52 </ul>53 )}54 </div>55 );56};5758export default BlogPosts;
Integrating React Router
Set up routing in src/App.tsx:
typescript1// src/App.tsx23import React from 'react';4import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';5import UploadSample from './components/UploadSample';6import PersonaList from './components/PersonaList';7import GenerateContent from './components/GenerateContent';8import BlogPosts from './components/BlogPosts';910const App: React.FC = () => {11 return (12 <Router>13 <nav>14 <ul>15 <li><Link to="/">Upload Sample</Link></li>16 <li><Link to="/personas">Personas</Link></li>17 <li><Link to="/blog-posts">Blog Posts</Link></li>18 </ul>19 </nav>20 <Routes>21 <Route path="/" element={<UploadSample />} />22 <Route path="/personas" element={<PersonaList />} />23 <Route path="/generate" element={<GenerateContent />} />24 <Route path="/blog-posts" element={<BlogPosts />} />25 </Routes>26 </Router>27 );28};2930export default App;
Setting Up Ollama and llama3.2
To analyze the writing samples and generate content, we'll use Ollama, a tool for running AI language models locally. We'll be using the llama3.2 model in this guide.
Installing Ollama
First, install Ollama on your machine. Ollama currently supports macOS.
For macOS:
If you have Homebrew installed, you can install Ollama by running:
bash1brew install ollama
If you don't have Homebrew, install it from here and then run the above command.
Downloading the llama3.2 Model
Once Ollama is installed, you can download the llama3.2 model:
bash1ollama pull llama3.2
This command will download and install the llama3.2 model locally.
Note: If llama3.2 is not available, replace it with the latest version of the Llama model supported by Ollama, such as llama2.
Running Ollama
Ollama runs as a background service. Start the Ollama server:
bash1ollama serve
This will start the server on http://localhost:11434, which is the default API endpoint for Ollama.
Testing the Model
To ensure everything is set up correctly, test the model using the Ollama CLI:
bash1ollama generate llama3.2 "Hello, how are you?"
You should see the model generate a response in your terminal.
Integrating Ollama with Django
Now that Ollama is running with the llama3.2 model, we'll integrate it into our Django application.
Installing python-decouple
We need python-decouple to manage environment variables. Install it using:
bash1pip install python-decouple
Configuring Environment Variables
Create a .env file in your backend directory to store sensitive information and environment variables:
bash1touch .env
Add the following line to your .env file:
OLLAMA_API_URL=http://localhost:11434/api/generate
This sets the API URL for Ollama.
Updating settings.py
Ensure that python-decouple is set up in your Django settings:
python1# backend/settings.py23from decouple import config45# ... rest of your settings ...67OLLAMA_API_URL = config('OLLAMA_API_URL', default='http://localhost:11434/api/generate')
Updating utils.py
Modify your analyze_writing_sample and generate_content functions in backend/core/utils.py to use Ollama and the llama3.2 model.
python1# core/utils.py23import logging4import requests5import json6from decouple import config78logger = logging.getLogger(__name__)9OLLAMA_API_URL = config('OLLAMA_API_URL', default='http://localhost:11434/api/generate')1011def analyze_writing_sample(writing_sample):12 encoding_prompt = f'''13Please analyze the writing style and personality of the given writing sample. Provide a detailed assessment of their characteristics using the following template. Rate each applicable characteristic on a scale of 1-10 where relevant, or provide a descriptive value. Return the results in a JSON format.1415"vocabulary_complexity": [1-10],16"sentence_structure": "[simple/complex/varied]",17# ... [rest of your JSON template] ...1819Writing Sample:20{writing_sample}21'''2223 payload = {24 'model': 'llama3.2', # Using llama3.2 model25 'prompt': encoding_prompt,26 'stream': False27 }28 headers = {'Content-Type': 'application/json'}2930 try:31 response = requests.post(OLLAMA_API_URL, json=payload, headers=headers)32 response.raise_for_status()33 response_text = response.json().get('response', '')34 analyzed_data = extract_json(response_text)35 if analyzed_data is None:36 logger.error("No JSON object found in the response.")37 return None38 return analyzed_data39 except (requests.RequestException, json.JSONDecodeError, AttributeError) as e:40 logger.error(f"Error during analyze_writing_sample: {str(e)}")41 return None4243def generate_content(persona_data, prompt):44 decoding_prompt = f'''45You are to write a blog post in the style of {persona_data.get('name', 'Unknown Author')}, a writer with the following characteristics:4647{json.dumps(persona_data, indent=2)}4849Now, please write a response in this style about the following topic:50"{prompt}"51Begin with a compelling title that reflects the content of the post.52'''5354 payload = {55 'model': 'llama3.2', # Using llama3.2 model56 'prompt': decoding_prompt,57 'stream': False58 }59 headers = {'Content-Type': 'application/json'}6061 try:62 logger.info(f"Sending request to Ollama API with payload: {payload}")63 response = requests.post(OLLAMA_API_URL, json=payload, headers=headers)64 response.raise_for_status()65 response_json = response.json()66 response_content = response_json.get('response', '').strip()67 if not response_content:68 logger.error("Ollama API response 'response' field is empty.")69 return ''70 return response_content71 except requests.RequestException as e:72 logger.error(f"Error during generate_content: {e}")73 if e.response:74 logger.error(f"Ollama Response Status: {e.response.status_code}")75 logger.error(f"Ollama Response Body: {e.response.text}")76 return ''
Updating the extract_json Function
Modify the extract_json function to parse the JSON data correctly:
python1def extract_json(response_text):2 try:3 # If the response contains extra text, extract the JSON object using regex4 json_str = re.search(r'\{.*\}', response_text, re.DOTALL).group()5 json_data = json.loads(json_str)6 return json_data7 except (json.JSONDecodeError, AttributeError) as e:8 logger.error(f"JSON decoding failed: {e}")9 return None
This function uses regular expressions to find the JSON object within the response text.
Testing the Integration
Restart the Django development server to apply the changes:
bash1python manage.py runserver
Ensure that Ollama is running and the llama3.2 model is loaded.
Using the Application
Now, when you use the frontend to upload a writing sample, the backend will:
- Send the writing sample to Ollama's API with the
llama3.2model. - Receive the analysis in JSON format.
- Store the analysis in the
Personamodel. - Generate content based on the persona data when prompted.
Running and Testing the Application
Starting the Backend
In the backend directory, start the Django development server:
bash1python manage.py runserver
Starting the Frontend
In the frontend directory, start the React development server:
bash1npm start
Testing the Application
-
Upload a Writing Sample:
- Navigate to
http://localhost:3000/. - Fill in the persona name and paste a writing sample.
- Submit the form to create a new persona.
- Navigate to
-
View Saved Personas:
- Navigate to
http://localhost:3000/personas. - See the list of personas you've created.
- Navigate to
-
Generate Content:
- From the personas list, click "Generate Content" next to a persona.
- Enter a prompt or topic.
- Generate content styled after the selected persona.
-
View Blog Posts:
- Navigate to
http://localhost:3000/blog-posts. - Read the generated blog posts.
- Navigate to
Conclusion
By setting up Ollama and integrating the llama3.2 model, your application can now analyze writing samples and generate content using AI capabilities locally. This enhances the functionality of your application, allowing for personalized content generation.
Final Steps:
- Ensure Ollama Starts on Boot: Consider configuring Ollama to start automatically when your system boots if you plan to use it frequently.
- Model Updates: Keep an eye on updates to Ollama and available models to enhance your application's capabilities.
- Resource Management: Running AI models locally can consume significant resources. Monitor system performance and adjust as necessary.
References:
Note: Replace 'llama3.2' with the appropriate model name if llama3.2 is not available or if you are using a different model supported by Ollama.
Let me know if you have any questions or need further assistance!

Sovereign AI: Building Local-First Intelligent Systems
by Daniel Kliewer · Paperback · 72 pages
The hands-on guide to building AI that runs on your hardware, keeps your data private, and eliminates cloud dependence. Working code included.