using the following code, improve it by making it better: i…
2024-10-232 turns48,463 charso1-mini
Summary
refactor and optimize the provided code for better performance
Messages
using the following code, improve it by making it better: import logging
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, generics
from .serializers import PersonaSerializer, BlogPostSerializer
from .models import Persona, BlogPost
from .utils import generate_content
logger = logging.getLogger(__name__)
class AnalyzeWritingSampleView(APIView):
def post(self, request, *args, **kwargs):
logger.debug(f"Request data: {request.data}")
serializer = PersonaSerializer(data=request.data)
if serializer.is_valid():
persona = serializer.save()
return Response(PersonaSerializer(persona).data, status=status.HTTP_201_CREATED)
else:
logger.error(f"Serializer validation failed: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class GenerateContentView(APIView):
def post(self, request):
persona_id = request.data.get('persona_id')
prompt = request.data.get('prompt')
if not persona_id:
logger.warning('persona_id is required.')
return Response({'error': 'persona_id is required.'}, status=status.HTTP_400_BAD_REQUEST)
if not prompt:
logger.warning('prompt is required.')
return Response({'error': 'prompt is required.'}, status=status.HTTP_400_BAD_REQUEST)
try:
persona = Persona.objects.get(id=persona_id)
except Persona.DoesNotExist:
logger.warning(f"Persona with ID {persona_id} not found.")
return Response({'error': f'Persona with ID {persona_id} not found'}, status=status.HTTP_404_NOT_FOUND)
# Call generate_content to create the blog post content
blog_post_content = generate_content(persona.data, prompt)
if not blog_post_content:
logger.error('Failed to generate blog post content.')
return Response({'error': 'Failed to generate blog post content.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# Ensure content is split into title and body
lines = blog_post_content.strip().split('\n')
title = lines[0] if lines else 'Untitled'
content = '\n'.join(lines[1:]) if len(lines) > 1 else ''
# Create the BlogPost object and save it to the database
blog_post = BlogPost.objects.create(
persona=persona,
title=title,
content=content,
)
# Log for debugging purposes
logger.debug(f"Blog post created with title: {title}")
# Return the serialized blog post data
return Response(BlogPostSerializer(blog_post).data, status=status.HTTP_201_CREATED)
class PersonaListView(generics.ListAPIView):
queryset = Persona.objects.all()
serializer_class = PersonaSerializer
class PersonaDetailView(APIView):
def get(self, request, persona_id):
try:
persona = Persona.objects.get(id=persona_id)
except Persona.DoesNotExist:
logger.warning(f"Persona with ID {persona_id} not found.")
return Response({'error': 'Persona not found'}, status=status.HTTP_404_NOT_FOUND)
serializer = PersonaSerializer(persona)
return Response(serializer.data, status=status.HTTP_200_OK)
class BlogPostView(generics.ListAPIView):
queryset = BlogPost.objects.all().order_by('-created_at')
serializer_class = BlogPostSerializer
import logging
import requests
import json
import re
from decouple import config
logger = logging.getLogger(__name__)
XAI_API_KEY = config('XAI_API_KEY')
XAI_API_BASE = "https://api.x.ai/v1"
def analyze_writing_sample(writing_sample):
endpoint = f"{XAI_API_BASE}/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {XAI_API_KEY}"
}
payload = {
"messages": [
{
"role": "system",
"content": "You are an assistant that analyzes writing samples."
},
{
"role": "user",
"content": f'''
Please 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.
"name": "[Author/Character Name]",
"vocabulary_complexity": [1-10],
"sentence_structure": "[simple/complex/varied]",
"paragraph_organization": "[structured/loose/stream-of-consciousness]",
"idiom_usage": [1-10],
"metaphor_frequency": [1-10],
"simile_frequency": [1-10],
"tone": "[formal/informal/academic/conversational/etc.]",
"punctuation_style": "[minimal/heavy/unconventional]",
"contraction_usage": [1-10],
"pronoun_preference": "[first-person/third-person/etc.]",
"passive_voice_frequency": [1-10],
"rhetorical_question_usage": [1-10],
"list_usage_tendency": [1-10],
"personal_anecdote_inclusion": [1-10],
"pop_culture_reference_frequency": [1-10],
"technical_jargon_usage": [1-10],
"parenthetical_aside_frequency": [1-10],
"humor_sarcasm_usage": [1-10],
"emotional_expressiveness": [1-10],
"emphatic_device_usage": [1-10],
"quotation_frequency": [1-10],
"analogy_usage": [1-10],
"sensory_detail_inclusion": [1-10],
"onomatopoeia_usage": [1-10],
"alliteration_frequency": [1-10],
"word_length_preference": "[short/long/varied]",
"foreign_phrase_usage": [1-10],
"rhetorical_device_usage": [1-10],
"statistical_data_usage": [1-10],
"personal_opinion_inclusion": [1-10],
"transition_usage": [1-10],
"reader_question_frequency": [1-10],
"imperative_sentence_usage": [1-10],
"dialogue_inclusion": [1-10],
"regional_dialect_usage": [1-10],
"hedging_language_frequency": [1-10],
"language_abstraction": "[concrete/abstract/mixed]",
"personal_belief_inclusion": [1-10],
"repetition_usage": [1-10],
"subordinate_clause_frequency": [1-10],
"verb_type_preference": "[active/stative/mixed]",
"sensory_imagery_usage": [1-10],
"symbolism_usage": [1-10],
"digression_frequency": [1-10],
"formality_level": [1-10],
"reflection_inclusion": [1-10],
"irony_usage": [1-10],
"neologism_frequency": [1-10],
"ellipsis_usage": [1-10],
"cultural_reference_inclusion": [1-10],
"stream_of_consciousness_usage": [1-10],
"openness_to_experience": [1-10],
"conscientiousness": [1-10],
"extraversion": [1-10],
"agreeableness": [1-10],
"emotional_stability": [1-10],
"dominant_motivations": "[achievement/affiliation/power/etc.]",
"core_values": "[integrity/freedom/knowledge/etc.]",
"decision_making_style": "[analytical/intuitive/spontaneous/etc.]",
"empathy_level": [1-10],
"self_confidence": [1-10],
"risk_taking_tendency": [1-10],
"idealism_vs_realism": "[idealistic/realistic/mixed]",
"conflict_resolution_style": "[assertive/collaborative/avoidant/etc.]",
"relationship_orientation": "[independent/communal/mixed]",
"emotional_response_tendency": "[calm/reactive/intense]",
"creativity_level": [1-10],
"age": "[age or age range]",
"gender": "[gender]",
"education_level": "[highest level of education]",
"professional_background": "[brief description]",
"cultural_background": "[brief description]",
"primary_language": "[language]",
"language_fluency": "[native/fluent/intermediate/beginner]",
"background": "[A brief paragraph describing the author's context, major influences, and any other relevant information not captured above]"
Writing Sample:
{writing_sample}
'''
}
],
"model": "grok-beta",
"stream": False,
"temperature": 0
}
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status() # Raises HTTPError for bad responses
# Log the API response for debugging
logger.debug(f"OpenAI API response: {response.text}")
assistant_message = response.json()['choices'][0]['message']['content'].strip()
logger.debug(f"Assistant message: {assistant_message}")
# Extract JSON from the assistant's message
json_str = re.search(r'\{.*\}', assistant_message, re.DOTALL)
if json_str:
analyzed_data = json.loads(json_str.group())
else:
logger.error("No JSON object found in the response.")
return None
return analyzed_data
except requests.exceptions.RequestException as e:
logger.error(f"HTTP Request failed: {e}")
return None
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error: {e}")
return None
def generate_content(persona_data, prompt):
endpoint = f"{XAI_API_BASE}/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {XAI_API_KEY}"
}
# Format the persona data into a readable string
characteristics = '\n'.join([
f"{key.replace('_', ' ').capitalize()}: {value}"
for key, value in persona_data.items()
if value is not None and key not in ['id', 'name']
])
decoding_prompt = f'''
You are to write a response in the style of {persona_data.get('name', 'Unknown Author')}, a writer with the following characteristics:
{characteristics}
Now, please write a response in this style about the following topic:
"{prompt}"
Begin with a compelling title that reflects the content of the post.
'''
payload = {
"messages": [
{"role": "system", "content": "You are an assistant that generates blog posts."},
{"role": "user", "content": decoding_prompt}
],
"model": "grok-beta",
"stream": False,
"temperature": 0
}
try:
response = requests.post(endpoint, headers=headers, json=payload)
response.raise_for_status()
assistant_message = response.json()['choices'][0]['message']['content'].strip()
logger.debug(f"Assistant message: {assistant_message}")
return assistant_message
except requests.exceptions.RequestException as e:
logger.error(f"HTTP Request failed: {e}")
return ''
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
return ''
except Exception as e:
logger.error(f"Unexpected error: {e}")
return ''
def save_blog_post(blog_post, title):
# Implement if needed
pass
from django.urls import path
from .views import (
AnalyzeWritingSampleView,
GenerateContentView,
PersonaListView,
PersonaDetailView,
BlogPostView
)
urlpatterns = [
path('analyze/', AnalyzeWritingSampleView.as_view(), name='analyze-writing-sample'),
path('generate-content/', GenerateContentView.as_view(), name='generate-content'),
path('personas/', PersonaListView.as_view(), name='persona-list'),
path('personas/<int:persona_id>/', PersonaDetailView.as_view(), name='persona-detail'),
path('blog-posts/', BlogPostView.as_view(), name='blogpost-list'),
]
from rest_framework import serializers
from .models import Persona, BlogPost
from .utils import analyze_writing_sample
import logging
logger = logging.getLogger(__name__)
class PersonaSerializer(serializers.ModelSerializer):
writing_sample = serializers.CharField(write_only=True)
class Meta:
model = Persona
fields = ['id', 'name', 'writing_sample', 'data']
read_only_fields = ['id', 'data']
def create(self, validated_data):
writing_sample = validated_data.pop('writing_sample')
logger.debug(f"Writing sample received: {writing_sample[:100]}...") # Log first 100 characters
# Analyze the writing sample
analyzed_data = analyze_writing_sample(writing_sample)
logger.debug(f"Analyzed data: {analyzed_data}")
if not analyzed_data:
logger.error("analyze_writing_sample returned empty or invalid data.")
raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."})
# Store the analyzed data
validated_data['data'] = analyzed_data
# Save persona
return Persona.objects.create(**validated_data)
class BlogPostSerializer(serializers.ModelSerializer):
persona = serializers.StringRelatedField() # Displays persona name
class Meta:
model = BlogPost
fields = ['id', 'persona', 'title', 'content', 'created_at']
from django.db import models
class Persona(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
data = models.JSONField(blank=True, null=True) # Stores analyzed writing sample data
def __str__(self):
return self.name
class BlogPost(models.Model):
id = models.BigAutoField(primary_key=True)
persona = models.ForeignKey(Persona, on_delete=models.CASCADE, related_name='blog_posts')
title = models.CharField(max_length=200, blank=True, null=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title or f"BlogPost {self.id}"
"""
Django settings for backend project.
Generated by 'django-admin startproject' using Django 5.1.2.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
# Ensure you have python-decouple installed:
# pip install python-decouple
import decouple
from decouple import config
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-afq-u-)(^1od2wb_(4oke^ky00%p-0$+o%e6bfw0nucj30$0$r'
XAI_API_KEY = config('XAI_API_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'core',
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'backend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'backend.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
'http://localhost:3001',
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('core.urls')), # Prefix API URLs with /api/
]
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import UploadSample from './components/UploadSample';
import PersonaList from './components/PersonaList';
import GenerateContent from './components/GenerateContent';
import BlogPosts from './components/BlogPosts';
import NavBar from './components/NavBar';
const App: React.FC = () => {
return (
<Router>
<NavBar />
<div style={{ padding: '20px' }}>
<Routes>
<Route path="/" element={<UploadSample />} />
<Route path="/personas" element={<PersonaList />} />
<Route path="/generate" element={<GenerateContent />} />
<Route path="/blog-posts" element={<BlogPosts />} />
</Routes>
</div>
</Router>
);
};
export default App;
import axios from 'axios';
const instance = axios.create({
baseURL: 'http://localhost:8000/api/', // Adjust the baseURL if needed
});
export default instance;
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client'; // Updated for React 18
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
import React, { useEffect, useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { CircularProgress, Typography, Box, Card, CardContent } from '@mui/material';
interface BlogPost {
id: number;
persona: string;
title: string;
content: string;
created_at: string;
}
const BlogPosts: React.FC = () => {
const [blogPosts, setBlogPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchBlogPosts = async () => {
try {
const response = await axios.get('blog-posts/');
setBlogPosts(response.data);
} catch (err) {
console.error('Error fetching blog posts:', err);
setError('Failed to load blog posts.');
} finally {
setLoading(false);
}
};
fetchBlogPosts();
}, []);
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" height="100vh">
<CircularProgress />
</Box>
);
}
if (error) {
return (
<Box display="flex" justifyContent="center" alignItems="center" height="100vh">
<Typography variant="h6" color="error">
{error}
</Typography>
</Box>
);
}
return (
<Box p={4}>
<Typography variant="h4" gutterBottom>
Output
</Typography>
{blogPosts.length === 0 ? (
<Typography variant="body1">No blog posts found.</Typography>
) : (
blogPosts.map((post) => (
<Card key={post.id} variant="outlined" sx={{ mb: 2 }}>
<CardContent>
<Typography variant="h5" gutterBottom>
{post.title || 'Untitled'}
</Typography>
<Typography variant="body2" paragraph>
{post.content}
</Typography>
<Typography variant="caption" color="text.secondary">
By: {post.persona} on {new Date(post.created_at).toLocaleString()}
</Typography>
</CardContent>
</Card>
))
)}
</Box>
);
};
export default BlogPosts;
import React, { useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { useSearchParams } from 'react-router-dom';
import { Box, Button, TextField, Typography, Alert, CircularProgress, Card, CardContent } from '@mui/material';
interface BlogPost {
id: number;
persona: string;
title: string;
content: string;
created_at: string;
}
const GenerateContent: React.FC = () => {
const [searchParams] = useSearchParams();
const personaIdParam = searchParams.get('personaId');
const personaId = personaIdParam ? Number(personaIdParam) : null;
const [prompt, setPrompt] = useState<string>('');
const [content, setContent] = useState<BlogPost | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const handleGenerate = async () => {
if (!prompt) {
setError('Please enter a prompt.');
return;
}
if (!personaId) {
setError('Invalid Persona ID.');
return;
}
setLoading(true);
setError(null);
try {
const response = await axios.post('generate-content/', {
persona_id: personaId,
prompt: prompt,
});
setContent(response.data);
setError(null);
setPrompt('');
} catch (err: any) {
console.error('Error generating content:', err);
if (err.response && err.response.data) {
setError(JSON.stringify(err.response.data));
} else {
setError('Failed to generate content.');
}
} finally {
setLoading(false);
}
};
return (
<Box p={4} maxWidth="600px" mx="auto">
<Typography variant="h4" gutterBottom>
Generate Content
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<TextField
label="Prompt"
variant="outlined"
fullWidth
multiline
rows={4}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Enter a topic or prompt..."
required
sx={{ mb: 3 }}
/>
<Button
onClick={handleGenerate}
variant="contained"
color="primary"
disabled={loading}
fullWidth
>
{loading ? <CircularProgress size={24} /> : 'Generate Content'}
</Button>
{content && (
<Card variant="outlined" sx={{ mt: 4 }}>
<CardContent>
<Typography variant="h5" gutterBottom>
{content.title || 'Untitled'}
</Typography>
<Typography variant="body1">
{content.content}
</Typography>
</CardContent>
</Card>
)}
</Box>
);
};
export default GenerateContent;
import { AppBar, Toolbar, Tabs, Tab, Box } from '@mui/material';
import { Link, useLocation, LinkProps } from 'react-router-dom';
import { styled } from '@mui/system';
import { TabProps } from '@mui/material/Tab'; // Import TabProps
const NavBar: React.FC = () => {
const location = useLocation();
const currentPath = location.pathname;
const StyledTab = styled(Tab)<TabProps & LinkProps>(({ theme }) => ({ // Extend with LinkProps
color: '#ffffff',
fontWeight: 'bold',
textTransform: 'none',
'&.Mui-selected': {
color: '#ffffff',
backgroundColor: '#333333',
borderRadius: theme.spacing(0.5),
},
'&:hover': {
color: '#aaaaaa',
},
}));
const StyledAppBar = styled(AppBar)({
backgroundColor: '#000000',
boxShadow: 'none',
});
return (
<StyledAppBar position="static">
<Toolbar>
<Box sx={{ flexGrow: 1 }}>
<Tabs value={currentPath} TabIndicatorProps={{ style: { backgroundColor: '#ffffff' } }}>
<StyledTab label="Upload Sample" value="/" component={Link} to="/" />
<StyledTab label="Personas" value="/personas" component={Link} to="/personas" />
<StyledTab label="Blog Posts" value="/blog-posts" component={Link} to="/blog-posts" />
</Tabs>
</Box>
</Toolbar>
</StyledAppBar>
);
};
export default NavBar;
/* General Container Styling */
.persona-list-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Title Styling */
.title {
text-align: center;
font-size: 2rem;
margin-bottom: 20px;
color: #333;
}
/* Loading and Error Messages */
.loading,
.error,
.no-personas {
text-align: center;
font-size: 1.2rem;
color: #666;
}
/* Persona Card Container */
.persona-cards {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
}
/* Persona Card Styling */
.persona-card {
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
width: calc(33.33% - 20px);
min-width: 200px;
text-align: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.persona-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* Persona Name Styling */
.persona-name {
font-size: 1.5rem;
margin-bottom: 15px;
color: #444;
}
/* Button Styling */
.generate-button {
background-color: #007bff;
color: #fff;
border: none;
padding: 10px 15px;
font-size: 1rem;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
}
.generate-button:hover {
background-color: #0056b3;
}
import React, { useEffect, useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { useNavigate } from 'react-router-dom';
import './PersonaList.css'; // Import the CSS file for styling
interface Persona {
id: number;
name: string;
data: Record<string, any>;
}
const PersonaList: React.FC = () => {
const [personas, setPersonas] = useState<Persona[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
const fetchPersonas = async () => {
try {
const response = await axios.get('personas/');
setPersonas(response.data);
} catch (err) {
console.error('Error fetching personas:', err);
setError('Failed to load personas.');
} finally {
setLoading(false);
}
};
fetchPersonas();
}, []);
const handleSelectPersona = (personaId: number) => {
navigate(`/generate?personaId=${personaId}`);
};
if (loading) return <div className="loading">Loading...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="persona-list-container">
<h2 className="title">Saved Personas</h2>
{personas.length === 0 ? (
<p className="no-personas">No personas found.</p>
) : (
<div className="persona-cards">
{personas.map((persona) => (
<div key={persona.id} className="persona-card">
<h3 className="persona-name">{persona.name}</h3>
<button
className="generate-button"
onClick={() => handleSelectPersona(persona.id)}
>
Generate Content
</button>
</div>
))}
</div>
)}
</div>
);
};
export default PersonaList;
import React, { useState } from 'react';
import axios from '../axiosConfig'; // Adjust the path if necessary
import { Box, Button, TextField, Typography, Alert, Stack } from '@mui/material';
const UploadSample: React.FC = () => {
const [name, setName] = useState('');
const [writingSample, setWritingSample] = useState('');
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const payload = {
name: name.trim(),
writing_sample: writingSample.trim(),
};
try {
console.log('Payload being sent:', payload);
const response = await axios.post('analyze/', payload);
console.log('Response received:', response.data);
setSuccess(`Persona "${response.data.name}" created successfully!`);
setError(null);
setName('');
setWritingSample('');
} catch (error: any) {
console.error('Error uploading writing sample:', error);
console.log('Error response:', error.response);
if (error.response && error.response.data) {
setError(JSON.stringify(error.response.data));
} else {
setError('An error occurred while uploading the writing sample.');
}
setSuccess(null);
}
};
return (
<Box p={4} maxWidth="600px" mx="auto">
<Typography variant="h4" gutterBottom>
Upload Writing Sample
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{success && (
<Alert severity="success" sx={{ mb: 2 }}>
{success}
</Alert>
)}
<form onSubmit={handleSubmit}>
<Stack spacing={3}>
<TextField
label="Persona Name"
variant="outlined"
fullWidth
value={name}
onChange={(e) => setName(e.target.value)}
required
inputProps={{ maxLength: 100 }}
/>
<TextField
label="Writing Sample"
variant="outlined"
fullWidth
multiline
rows={6}
value={writingSample}
onChange={(e) => setWritingSample(e.target.value)}
required
/>
<Button type="submit" variant="contained" color="primary" size="large">
Submit
</Button>
</Stack>
</form>
</Box>
);
};
export default UploadSample;
Certainly! Your project showcases a comprehensive integration between a Django backend and a React frontend, leveraging REST APIs and external AI services. To enhance the overall quality, maintainability, and performance of your codebase, consider the following improvements:
## **Backend Improvements (Django REST Framework)**
### 1. **Refactor and Organize Imports**
- **Avoid Duplicate Imports**: Ensure that each module is imported only once.
- **Group Related Imports**: Separate standard library imports, third-party imports, and local imports for better readability.
**Before:**
```python
import logging
from rest_framework.views import APIView
from rest_framework.response import Response
# ... other imports
import logging
import requests
import json
import re
from decouple import config
# ... other imports
import logging
```
**After:**
```python
import logging
import re
import json
from pathlib import Path
import requests
from decouple import config
from django.db import models
from django.urls import path
from rest_framework import status, generics, serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Persona, BlogPost
from .serializers import PersonaSerializer, BlogPostSerializer
from .utils import generate_content, analyze_writing_sample
```
### 2. **Use Generic Views for CRUD Operations**
Leverage Django REST Framework's generic views to reduce boilerplate code for common operations.
**Example:**
Instead of using `APIView` for `PersonaDetailView`, use `RetrieveAPIView`:
```python
from rest_framework import generics
class PersonaDetailView(generics.RetrieveAPIView):
queryset = Persona.objects.all()
serializer_class = PersonaSerializer
lookup_field = 'id'
lookup_url_kwarg = 'persona_id'
```
### 3. **Implement Pagination for List Views**
To handle large datasets efficiently, implement pagination in your list views.
**Example:**
```python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}
```
```python
# views.py
class PersonaListView(generics.ListAPIView):
queryset = Persona.objects.all()
serializer_class = PersonaSerializer
pagination_class = PageNumberPagination # Optional if set globally
```
### 4. **Enhance Error Handling and Logging**
- **Avoid Logging Sensitive Information**: Ensure that sensitive data (like API keys or user data) is not logged.
- **Use Custom Exceptions**: Create custom exceptions for better error categorization.
- **Consistent Logging Levels**: Use appropriate logging levels (`debug`, `info`, `warning`, `error`, `critical`).
**Example:**
```python
# utils.py
def analyze_writing_sample(writing_sample):
try:
# API call logic...
except requests.exceptions.RequestException as e:
logger.error(f"HTTP Request failed: {e}")
raise CustomAPIException("External API request failed.") from e
except json.JSONDecodeError as e:
logger.error(f"JSON decoding failed: {e}")
raise CustomAPIException("Invalid response format from external API.") from e
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise CustomAPIException("An unexpected error occurred.") from e
```
```python
# exceptions.py
from rest_framework.exceptions import APIException
class CustomAPIException(APIException):
status_code = 500
default_detail = 'A server error occurred.'
default_code = 'server_error'
```
### 5. **Optimize Serializer Performance**
- **Use `select_related` or `prefetch_related`**: Optimize database queries to prevent the "N+1" problem.
**Example:**
```python
class BlogPostView(generics.ListAPIView):
queryset = BlogPost.objects.select_related('persona').all().order_by('-created_at')
serializer_class = BlogPostSerializer
```
- **Limit Serializer Fields**: If certain endpoints don't require all fields, create separate serializers to limit data transfer.
### 6. **Secure API Endpoints**
- **Authentication and Permissions**: Implement authentication (e.g., JWT, OAuth) and set appropriate permissions to protect your endpoints.
**Example:**
```python
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
class GenerateContentView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
# Rest of the view...
```
- **Rate Limiting**: Prevent abuse by implementing rate limiting using packages like `django-ratelimit`.
### 7. **Environment Variable Management**
- **Default Values and Validation**: Provide default values and validate critical environment variables during startup.
**Example:**
```python
# settings.py
XAI_API_KEY = config('XAI_API_KEY', default=None)
if not XAI_API_KEY:
raise ValueError("XAI_API_KEY is not set in the environment variables.")
```
### 8. **Asynchronous Tasks for External API Calls**
To improve responsiveness, handle long-running external API calls asynchronously using Celery or Django Q.
**Example with Celery:**
```python
# tasks.py
from celery import shared_task
@shared_task
def analyze_writing_sample_task(writing_sample):
return analyze_writing_sample(writing_sample)
```
```python
# views.py
from .tasks import analyze_writing_sample_task
class AnalyzeWritingSampleView(APIView):
def post(self, request, *args, **kwargs):
serializer = PersonaSerializer(data=request.data)
if serializer.is_valid():
persona = serializer.save()
analyze_writing_sample_task.delay(persona.id)
return Response(PersonaSerializer(persona).data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
```
## **Frontend Improvements (React)**
### 1. **Centralize API Configuration**
- **Environment Variables**: Use environment variables for API base URLs to easily switch between development and production.
**Example:**
```env
# .env
REACT_APP_API_BASE_URL=https://api.yourdomain.com/api/
```
```javascript
// axiosConfig.js
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:8000/api/',
});
export default instance;
```
### 2. **Improve Error Handling and User Feedback**
- **Consistent Error Messages**: Standardize how errors are displayed to users.
- **Retry Mechanism**: Implement retries for transient errors using libraries like `axios-retry`.
**Example:**
```javascript
import axios from 'axios';
import axiosRetry from 'axios-retry';
const instance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:8000/api/',
});
// Retry failed requests up to 3 times
axiosRetry(instance, { retries: 3, retryDelay: axiosRetry.exponentialDelay });
export default instance;
```
### 3. **Use TypeScript Interfaces Consistently**
Ensure all components and functions utilize TypeScript interfaces for type safety and better developer experience.
**Example:**
```typescript
// types.d.ts
export interface Persona {
id: number;
name: string;
data: Record<string, any>;
}
export interface BlogPost {
id: number;
persona: string;
title: string;
content: string;
created_at: string;
}
```
```typescript
// components/BlogPosts.tsx
import { BlogPost } from '../types';
const [blogPosts, setBlogPosts] = useState<BlogPost[]>([]);
```
### 4. **Optimize Component Performance**
- **Memoization**: Use `React.memo` and `useMemo` to prevent unnecessary re-renders.
- **Code Splitting**: Implement lazy loading for routes to improve initial load times.
**Example:**
```javascript
// App.tsx
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import NavBar from './components/NavBar';
import { CircularProgress } from '@mui/material';
const UploadSample = lazy(() => import('./components/UploadSample'));
const PersonaList = lazy(() => import('./components/PersonaList'));
const GenerateContent = lazy(() => import('./components/GenerateContent'));
const BlogPosts = lazy(() => import('./components/BlogPosts'));
const App: React.FC = () => (
<Router>
<NavBar />
<div style={{ padding: '20px' }}>
<Suspense fallback={<CircularProgress />}>
<Routes>
<Route path="/" element={<UploadSample />} />
<Route path="/personas" element={<PersonaList />} />
<Route path="/generate" element={<GenerateContent />} />
<Route path="/blog-posts" element={<BlogPosts />} />
</Routes>
</Suspense>
</div>
</Router>
);
export default App;
```
### 5. **Enhance Accessibility (a11y)**
- **Semantic HTML**: Use semantic elements to improve accessibility.
- **ARIA Attributes**: Add ARIA attributes where necessary.
- **Keyboard Navigation**: Ensure all interactive elements are accessible via keyboard.
**Example:**
```javascript
// NavBar.tsx
<StyledTab
label="Upload Sample"
value="/"
component={Link}
to="/"
aria-label="Upload Writing Sample"
/>
```
### 6. **Improve Styling with Theme Customization**
Leverage Material-UI's theming capabilities to maintain a consistent design system.
**Example:**
```javascript
// theme.ts
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#007bff',
},
background: {
default: '#f5f5f5',
},
},
typography: {
// Define typography settings
},
});
export default theme;
```
```javascript
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from '@mui/material/styles';
import theme from './theme';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</React.StrictMode>
);
```
### 7. **Implement State Management if Necessary**
For larger applications, consider using state management libraries like Redux or Zustand to manage global state more efficiently.
### 8. **Enhance Form Handling**
Use form management libraries like `Formik` or `React Hook Form` to simplify form handling and validation.
**Example with React Hook Form:**
```javascript
// UploadSample.tsx
import { useForm, SubmitHandler } from 'react-hook-form';
interface FormInputs {
name: string;
writingSample: string;
}
const UploadSample: React.FC = () => {
const { register, handleSubmit, formState: { errors }, reset } = useForm<FormInputs>();
const onSubmit: SubmitHandler<FormInputs> = async (data) => {
// Handle form submission
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
label="Persona Name"
{...register('name', { required: 'Name is required', maxLength: 100 })}
error={!!errors.name}
helperText={errors.name?.message}
/>
<TextField
label="Writing Sample"
{...register('writingSample', { required: 'Writing sample is required' })}
error={!!errors.writingSample}
helperText={errors.writingSample?.message}
/>
<Button type="submit">Submit</Button>
</form>
);
};
```
## **General Best Practices**
### 1. **Version Control and Documentation**
- **Git Best Practices**: Use meaningful commit messages, branch naming conventions, and pull request reviews.
- **Documentation**: Maintain up-to-date documentation using tools like Swagger for API docs and Storybook for frontend components.
### 2. **Testing**
- **Backend Testing**: Write unit and integration tests using Django's testing framework or `pytest`.
- **Frontend Testing**: Implement component and integration tests using Jest and React Testing Library.
### 3. **Continuous Integration and Deployment (CI/CD)**
Set up CI/CD pipelines using platforms like GitHub Actions, GitLab CI, or Jenkins to automate testing, building, and deployment processes.
### 4. **Security Enhancements**
- **HTTPS Enforcement**: Ensure that both frontend and backend are served over HTTPS in production.
- **CORS Configuration**: Fine-tune CORS settings to allow only trusted origins.
- **Input Validation**: Validate all inputs on both frontend and backend to prevent injection attacks.
**Example:**
```python
# settings.py
CORS_ALLOWED_ORIGINS = [
'https://yourdomain.com',
'https://www.yourdomain.com',
]
```
### 5. **Performance Optimization**
- **Backend**:
- Use caching mechanisms like Redis to cache frequent API responses.
- Optimize database indexes based on query patterns.
- **Frontend**:
- Optimize images and assets.
- Use code splitting and lazy loading to reduce initial load times.
### 6. **Accessibility and SEO (for Frontend)**
- **Accessibility**: Ensure all components are accessible, using tools like Lighthouse to audit.
- **SEO**: If applicable, optimize for SEO using server-side rendering (SSR) with frameworks like Next.js.
## **Sample Refactored Backend Code Snippets**
### **Refactored `GenerateContentView` with Improved Error Handling and Asynchronous Task**
```python
# views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from .tasks import generate_blog_post_task
class GenerateContentView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
persona_id = request.data.get('persona_id')
prompt = request.data.get('prompt')
if not persona_id:
logger.warning('persona_id is required.')
return Response({'error': 'persona_id is required.'}, status=status.HTTP_400_BAD_REQUEST)
if not prompt:
logger.warning('prompt is required.')
return Response({'error': 'prompt is required.'}, status=status.HTTP_400_BAD_REQUEST)
try:
persona = Persona.objects.get(id=persona_id)
except Persona.DoesNotExist:
logger.warning(f"Persona with ID {persona_id} not found.")
return Response({'error': f'Persona with ID {persona_id} not found'}, status=status.HTTP_404_NOT_FOUND)
# Asynchronously generate blog post content
generate_blog_post_task.delay(persona.id, prompt)
logger.debug(f"Blog post generation task initiated for persona ID: {persona_id}")
return Response({'message': 'Blog post generation initiated.'}, status=status.HTTP_202_ACCEPTED)
```
```python
# tasks.py
from celery import shared_task
from .utils import generate_content
@shared_task
def generate_blog_post_task(persona_id, prompt):
try:
persona = Persona.objects.get(id=persona_id)
blog_post_content = generate_content(persona.data, prompt)
if not blog_post_content:
logger.error('Failed to generate blog post content.')
return
lines = blog_post_content.strip().split('\n')
title = lines[0] if lines else 'Untitled'
content = '\n'.join(lines[1:]) if len(lines) > 1 else ''
BlogPost.objects.create(
persona=persona,
title=title,
content=content,
)
logger.debug(f"Blog post created with title: {title} for persona ID: {persona_id}")
except Exception as e:
logger.error(f"Error in generating blog post: {e}")
```
### **Refactored Serializer with Enhanced Validation**
```python
# serializers.py
class PersonaSerializer(serializers.ModelSerializer):
writing_sample = serializers.CharField(write_only=True)
class Meta:
model = Persona
fields = ['id', 'name', 'writing_sample', 'data']
read_only_fields = ['id', 'data']
def validate_writing_sample(self, value):
if len(value) < 100:
raise serializers.ValidationError("Writing sample must be at least 100 characters long.")
return value
def create(self, validated_data):
writing_sample = validated_data.pop('writing_sample')
logger.debug(f"Writing sample received: {writing_sample[:100]}...")
analyzed_data = analyze_writing_sample(writing_sample)
logger.debug(f"Analyzed data: {analyzed_data}")
if not analyzed_data:
logger.error("analyze_writing_sample returned empty or invalid data.")
raise serializers.ValidationError({"writing_sample": "Failed to analyze the writing sample."})
validated_data['data'] = analyzed_data
return Persona.objects.create(**validated_data)
```
## **Conclusion**
By implementing the above improvements, you can enhance the scalability, maintainability, and security of your application. Regularly reviewing and refactoring your code, adhering to best practices, and leveraging the strengths of both Django and React ecosystems will lead to a robust and efficient application.
Feel free to reach out if you need further assistance with specific parts of your project!
o1-mini