Debate System Integration
2024-12-062 turns37,201 charsgpt-4o
Summary
User wants to integrate React UI components into the PersonaGen07 repository.
Messages
write a prompt to integrate this code into this repo: https://github.com/kliewerdaniel/PersonaGen07 import React, { useState, useEffect } from 'react';
import {
Box,
Button,
TextField,
Typography,
Card,
CardContent,
FormControl,
InputLabel,
MenuItem,
Select,
Grid,
Alert
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
import axios from '../../../src/services/api';
interface Persona {
id: number;
name: string;
description: string;
}
const CreateDebate: React.FC = () => {
const navigate = useNavigate();
const [title, setTitle] = useState('');
const [topic, setTopic] = useState('');
const [personaA, setPersonaA] = useState<number | ''>('');
const [maxTurns, setMaxTurns] = useState<number>(10);
const [personas, setPersonas] = useState<Persona[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchPersonas();
}, []);
const fetchPersonas = async () => {
try {
const response = await axios.get('/api/personas/');
setPersonas(response.data);
} catch (err) {
setError('Failed to load personas');
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!title || !topic || !personaA) {
setError('Please fill in all required fields');
return;
}
setLoading(true);
try {
const response = await axios.post('/api/debates/', {
title,
topic,
persona_a: personaA,
max_turns: maxTurns
});
navigate(`/debates/${response.data.id}`);
} catch (err) {
setError('Failed to create debate');
} finally {
setLoading(false);
}
};
return (
<Box sx={{ p: 3 }}>
<Card>
<CardContent>
<Typography variant="h5" gutterBottom>
Create New Debate
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<form onSubmit={handleSubmit}>
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
fullWidth
label="Debate Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
label="Topic"
value={topic}
onChange={(e) => setTopic(e.target.value)}
multiline
rows={3}
required
helperText="Describe the topic of debate"
/>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth required>
<InputLabel>Initial Persona</InputLabel>
<Select
value={personaA}
onChange={(e) => setPersonaA(e.target.value as number)}
>
{personas.map((persona) => (
<MenuItem key={persona.id} value={persona.id}>
{persona.name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>Maximum Turns</InputLabel>
<Select
value={maxTurns}
onChange={(e) => setMaxTurns(e.target.value as number)}
>
{[5, 10, 15, 20].map((num) => (
<MenuItem key={num} value={num}>
{num} turns
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={loading}
fullWidth
>
{loading ? 'Creating...' : 'Start Debate'}
</Button>
</Grid>
</Grid>
</form>
</CardContent>
</Card>
</Box>
);
};
export default CreateDebate;
import React, { useCallback, useEffect, useState } from 'react';
import { ForceGraph2D } from 'react-force-graph';
import { Box, Typography, Card, CardContent, Grid } from '@mui/material';
interface DebateNode {
id: string;
content: string;
persona: string;
turn: number;
x?: number;
y?: number;
color?: string;
}
interface DebateEdge {
source: string;
target: string;
relevance: number;
persuasiveness: number;
civility: number;
}
interface DebateGraphData {
nodes: DebateNode[];
edges: DebateEdge[];
}
interface DebateGraphProps {
graphData: DebateGraphData;
onNodeClick?: (node: DebateNode) => void;
}
const DebateGraph: React.FC<DebateGraphProps> = ({ graphData, onNodeClick }) => {
const [dimensions, setDimensions] = useState({ width: 800, height: 600 });
useEffect(() => {
const updateDimensions = () => {
const container = document.getElementById('debate-graph-container');
if (container) {
setDimensions({
width: container.clientWidth,
height: Math.max(400, window.innerHeight * 0.6)
});
}
};
window.addEventListener('resize', updateDimensions);
updateDimensions();
return () => window.removeEventListener('resize', updateDimensions);
}, []);
const handleNodeClick = useCallback((node: DebateNode) => {
if (onNodeClick) {
onNodeClick(node);
}
}, [onNodeClick]);
// Convert data to format expected by ForceGraph2D
const graphDataFormatted = {
nodes: graphData.nodes.map(node => ({
...node,
color: node.persona === graphData.nodes[0].persona ? '#4CAF50' : '#2196F3'
})),
links: graphData.edges.map(edge => ({
source: edge.source,
target: edge.target,
value: edge.relevance
}))
};
return (
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Debate Flow Visualization
</Typography>
<Box
id="debate-graph-container"
sx={{
height: dimensions.height,
width: '100%',
position: 'relative'
}}
>
<ForceGraph2D
graphData={graphDataFormatted}
nodeLabel={node => `${(node as DebateNode).persona}: ${(node as DebateNode).content}`}
nodeColor={node => (node as DebateNode).color || '#666'}
linkDirectionalParticles={2}
linkDirectionalParticleSpeed={d => d.value * 0.01}
linkWidth={link => (link as any).value * 2}
nodeCanvasObject={(node, ctx, globalScale) => {
const label = (node as DebateNode).content.substring(0, 20) + '...';
const fontSize = 12/globalScale;
ctx.font = `${fontSize}px Sans-Serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = (node as DebateNode).color || '#666';
ctx.fillText(label, node.x!, node.y!);
}}
onNodeClick={handleNodeClick}
width={dimensions.width}
height={dimensions.height}
/>
</Box>
<Grid container spacing={2} sx={{ mt: 2 }}>
<Grid item xs={6}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 12,
height: 12,
bgcolor: '#4CAF50',
borderRadius: '50%',
mr: 1
}} />
<Typography variant="body2">
First Persona
</Typography>
</Box>
</Grid>
<Grid item xs={6}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 12,
height: 12,
bgcolor: '#2196F3',
borderRadius: '50%',
mr: 1
}} />
<Typography variant="body2">
Second Persona
</Typography>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
);
};
export default DebateGraph;
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
TextField,
Typography,
Card,
CardContent,
Grid,
Dialog,
DialogTitle,
DialogContent,
DialogActions
} from '@mui/material';
import axios from '../../../src/services/api';
import DebateGraph from './DebateGraph';
import { useParams } from 'react-router-dom';
interface DebateData {
id: number;
title: string;
topic: string;
persona_a: {
id: number;
name: string;
};
persona_b: {
id: number;
name: string;
};
is_active: boolean;
current_turn: number;
}
const DebateInterface: React.FC = () => {
const { debateId } = useParams<{ debateId: string }>();
const [debate, setDebate] = useState<DebateData | null>(null);
const [graphData, setGraphData] = useState(null);
const [message, setMessage] = useState('');
const [selectedNode, setSelectedNode] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchDebateData();
const interval = setInterval(fetchDebateData, 5000); // Poll for updates
return () => clearInterval(interval);
}, [debateId]);
const fetchDebateData = async () => {
try {
const [debateRes, graphRes] = await Promise.all([
axios.get(`/api/debates/${debateId}/`),
axios.get(`/api/debates/${debateId}/graph/`)
]);
setDebate(debateRes.data);
setGraphData(graphRes.data);
setLoading(false);
} catch (err) {
setError('Failed to load debate data');
setLoading(false);
}
};
const handleSendMessage = async () => {
if (!message.trim()) return;
try {
await axios.post(`/api/debates/${debateId}/add_message/`, {
content: message,
persona_id: debate?.persona_a.id // You might want to toggle between personas
});
setMessage('');
fetchDebateData();
} catch (err) {
setError('Failed to send message');
}
};
const handleNodeClick = (node: any) => {
setSelectedNode(node);
};
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography>Loading debate...</Typography>
</Box>
);
}
if (error) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography color="error">{error}</Typography>
</Box>
);
}
return (
<Box sx={{ p: 3 }}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h5" gutterBottom>
{debate?.title}
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Topic: {debate?.topic}
</Typography>
<Typography variant="body2">
Turn: {debate?.current_turn} / {debate?.max_turns}
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
{graphData && (
<DebateGraph
graphData={graphData}
onNodeClick={handleNodeClick}
/>
)}
</Grid>
{debate?.is_active && (
<Grid item xs={12}>
<Card>
<CardContent>
<TextField
fullWidth
multiline
rows={3}
variant="outlined"
placeholder="Type your message..."
value={message}
onChange={(e) => setMessage(e.target.value)}
sx={{ mb: 2 }}
/>
<Button
variant="contained"
color="primary"
onClick={handleSendMessage}
disabled={!message.trim()}
>
Send Message
</Button>
</CardContent>
</Card>
</Grid>
)}
</Grid>
<Dialog
open={Boolean(selectedNode)}
onClose={() => setSelectedNode(null)}
maxWidth="sm"
fullWidth
>
<DialogTitle>
Turn {selectedNode?.turn} - {selectedNode?.persona}
</DialogTitle>
<DialogContent>
<Typography>{selectedNode?.content}</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setSelectedNode(null)}>Close</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default DebateInterface;
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
Typography,
Card,
CardContent,
Grid,
Chip,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
import axios from '../../../src/services/api';
import { Add as AddIcon, Visibility as VisibilityIcon } from '@mui/icons-material';
interface Debate {
id: number;
title: string;
topic: string;
persona_a_name: string;
persona_b_name: string;
is_active: boolean;
current_turn: number;
max_turns: number;
created_at: string;
}
const DebateList: React.FC = () => {
const navigate = useNavigate();
const [debates, setDebates] = useState<Debate[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchDebates();
}, []);
const fetchDebates = async () => {
try {
const response = await axios.get('/api/debates/');
setDebates(response.data);
setLoading(false);
} catch (err) {
setError('Failed to load debates');
setLoading(false);
}
};
const handleCreateDebate = () => {
navigate('/debates/create');
};
const handleViewDebate = (id: number) => {
navigate(`/debates/${id}`);
};
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography>Loading debates...</Typography>
</Box>
);
}
if (error) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography color="error">{error}</Typography>
</Box>
);
}
return (
<Box sx={{ p: 3 }}>
<Grid container spacing={3}>
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h5">Debates</Typography>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={handleCreateDebate}
>
Create New Debate
</Button>
</Grid>
<Grid item xs={12}>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Topic</TableCell>
<TableCell>Personas</TableCell>
<TableCell>Progress</TableCell>
<TableCell>Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{debates.map((debate) => (
<TableRow key={debate.id}>
<TableCell>{debate.title}</TableCell>
<TableCell>{debate.topic}</TableCell>
<TableCell>
<Box>
<Typography variant="body2">{debate.persona_a_name}</Typography>
{debate.persona_b_name && (
<>
<Typography variant="body2" color="textSecondary" sx={{ my: 0.5 }}>
vs
</Typography>
<Typography variant="body2">{debate.persona_b_name}</Typography>
</>
)}
</Box>
</TableCell>
<TableCell>
{debate.current_turn} / {debate.max_turns} turns
</TableCell>
<TableCell>
<Chip
label={debate.is_active ? 'Active' : 'Completed'}
color={debate.is_active ? 'success' : 'default'}
size="small"
/>
</TableCell>
<TableCell>
<IconButton
color="primary"
onClick={() => handleViewDebate(debate.id)}
size="small"
>
<VisibilityIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
</Box>
);
};
export default DebateList;
import React, { useEffect, useState } from 'react';
import { Box, Typography, Card, CardContent, Grid } from '@mui/material';
import axios from '../../../services/api';
interface DebateNode {
id: string;
content: string;
persona: string;
turn: number;
}
interface DebateEdge {
source: string;
target: string;
relevance: number;
persuasiveness: number;
civility: number;
}
interface DebateGraphData {
nodes: DebateNode[];
edges: DebateEdge[];
}
interface DebateViewProps {
debateId: number;
}
const DebateView: React.FC<DebateViewProps> = ({ debateId }) => {
const [graphData, setGraphData] = useState<DebateGraphData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchDebateData = async () => {
try {
const response = await axios.get(`/api/debates/${debateId}/graph`);
setGraphData(response.data);
setLoading(false);
} catch (err) {
setError('Failed to load debate data');
setLoading(false);
}
};
fetchDebateData();
}, [debateId]);
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography>Loading debate...</Typography>
</Box>
);
}
if (error) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography color="error">{error}</Typography>
</Box>
);
}
if (!graphData) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight={400}>
<Typography>No debate data available</Typography>
</Box>
);
}
return (
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Debate Visualization
</Typography>
<Grid container spacing={2}>
{graphData.nodes.map((node) => (
<Grid item xs={12} key={node.id}>
<Card variant="outlined">
<CardContent>
<Typography variant="subtitle2" color="textSecondary">
Turn {node.turn} - {node.persona}
</Typography>
<Typography variant="body1">
{node.content}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</CardContent>
</Card>
);
};
export default DebateView;
"""Core functionality for the debate engine."""
from typing import Dict, Optional
from django.db import transaction
from .models import Persona, Debate, DebateMessage
from .utils import generate_content
class DebateEngineCore:
def __init__(self, debate: Debate):
self.debate = debate
def generate_opposing_persona(self, original_persona: Persona) -> Dict:
"""Generate attributes for an opposing persona based on original persona's characteristics."""
return {
# Adjust writing style characteristics
'vocabulary_complexity': min(100, max(0, 100 - original_persona.vocabulary_complexity)),
'sentence_structure': self._adjust_style(original_persona.sentence_structure),
'tone': self._adjust_tone(original_persona.tone),
'rhetorical_question_usage': min(100, max(0, 100 - original_persona.rhetorical_question_usage)),
'technical_jargon_usage': min(100, max(0, 100 - original_persona.technical_jargon_usage)),
'emotional_expressiveness': min(100, max(0, 100 - original_persona.emotional_expressiveness)),
'statistical_data_usage': min(100, max(0, 100 - original_persona.statistical_data_usage)),
'personal_opinion_inclusion': min(100, max(0, 100 - original_persona.personal_opinion_inclusion)),
'hedging_language_frequency': min(100, max(0, 100 - original_persona.hedging_language_frequency)),
# Personality traits (keeping some similar for consistency)
'openness_to_experience': original_persona.openness_to_experience,
'conscientiousness': original_persona.conscientiousness,
'extraversion': original_persona.extraversion,
'agreeableness': min(100, max(0, 100 - original_persona.agreeableness)),
'emotional_stability': original_persona.emotional_stability
}
def _adjust_style(self, original_style: str) -> str:
"""Adjust writing style to create contrast."""
style_pairs = {
'complex': 'simple',
'formal': 'casual',
'direct': 'elaborate',
'concise': 'detailed'
}
return style_pairs.get(original_style.lower(), 'balanced')
def _adjust_tone(self, original_tone: str) -> str:
"""Adjust tone to create contrast."""
tone_pairs = {
'assertive': 'inquisitive',
'formal': 'conversational',
'serious': 'light',
'objective': 'subjective',
'passionate': 'analytical'
}
return tone_pairs.get(original_tone.lower(), 'neutral')
def generate_message(self, persona: Persona, previous_message: Optional[str], topic: str) -> str:
"""Generate a debate message from a persona in response to a previous message."""
if not previous_message:
prompt = f"Start a debate on the topic: {topic}"
else:
prompt = f"Respond to this message in a debate about {topic}: {previous_message}"
return generate_content(persona, prompt)
@transaction.atomic
def add_message(self, persona: Persona, content: str) -> Optional[DebateMessage]:
"""Add a new message to the debate."""
if not self.debate.is_active or self.debate.current_turn >= self.debate.max_turns:
self.debate.is_active = False
self.debate.save()
return None
# Validate turn order
if self.debate.last_message:
if self.debate.last_message.persona == persona:
return None
# Create message
message = DebateMessage.objects.create(
debate=self.debate,
persona=persona,
content=content,
turn_number=self.debate.current_turn
)
# Update debate
self.debate.current_turn += 1
if self.debate.current_turn >= self.debate.max_turns:
self.debate.is_active = False
self.debate.save()
return message
def generate_debate_message(persona: Persona, previous_message: Optional[str], topic: str) -> str:
"""Helper function to generate a debate message without instantiating the class."""
engine = DebateEngineCore(None)
return engine.generate_message(persona, previous_message, topic)
"""Graph-based representation of debates."""
from typing import List, Dict
import networkx as nx
from .models import Debate, DebateMessage
class DebateGraph:
def __init__(self, debate: Debate):
self.debate = debate
self.graph = nx.DiGraph()
self._build_graph()
def _build_graph(self) -> None:
"""Construct the debate graph from messages."""
messages = self.debate.messages.all().order_by('turn_number')
prev_node = None
for msg in messages:
node_id = f"turn_{msg.turn_number}"
self.graph.add_node(node_id,
content=msg.content,
persona=msg.persona.name,
turn=msg.turn_number)
if prev_node:
# Calculate edge weights based on persona characteristics
edge_weights = self._calculate_edge_weights(msg)
self.graph.add_edge(prev_node, node_id, **edge_weights)
prev_node = node_id
def _calculate_edge_weights(self, msg: DebateMessage) -> Dict[str, float]:
"""Calculate edge weights based on persona characteristics."""
persona = msg.persona
# Calculate relevance based on technical and analytical characteristics
relevance = (
persona.technical_jargon_usage +
persona.statistical_data_usage +
persona.vocabulary_complexity
) / 300 # Normalize to [0,1]
# Calculate persuasiveness based on rhetorical and emotional characteristics
persuasiveness = (
persona.rhetorical_question_usage +
persona.emotional_expressiveness +
persona.personal_opinion_inclusion
) / 300
# Calculate civility based on personality traits
civility = (
persona.agreeableness +
persona.emotional_stability +
persona.conscientiousness
) / 300
return {
'relevance': min(1.0, max(0.0, relevance)),
'persuasiveness': min(1.0, max(0.0, persuasiveness)),
'civility': min(1.0, max(0.0, civility))
}
def get_strongest_path(self, weight_type: str = 'relevance') -> List[str]:
"""Return the path with highest total weight."""
if self.debate.current_turn < 2:
return []
paths = nx.all_simple_paths(self.graph,
source=f"turn_0",
target=f"turn_{self.debate.current_turn-1}")
max_weight = -1
strongest_path = None
for path in paths:
weight = sum(self.graph[path[i]][path[i+1]][weight_type]
for i in range(len(path)-1))
if weight > max_weight:
max_weight = weight
strongest_path = path
return strongest_path or []
def get_graph_data(self) -> Dict:
"""Return graph data in a format suitable for visualization."""
return {
'nodes': [
{
'id': node,
'content': data['content'],
'persona': data['persona'],
'turn': data['turn']
}
for node, data in self.graph.nodes(data=True)
],
'edges': [
{
'source': u,
'target': v,
'weights': {
'relevance': data['relevance'],
'persuasiveness': data['persuasiveness'],
'civility': data['civility']
}
}
for u, v, data in self.graph.edges(data=True)
]
}
from django.db import models
from .models import Persona
class Debate(models.Model):
"""A debate session between two personas."""
title = models.CharField(max_length=200)
topic = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# The two personas involved in the debate
persona_a = models.ForeignKey(Persona, on_delete=models.CASCADE, related_name='debates_as_a')
persona_b = models.ForeignKey(Persona, on_delete=models.CASCADE, related_name='debates_as_b')
# Debate parameters
max_turns = models.IntegerField(default=5)
current_turn = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
def __str__(self):
return f"Debate: {self.title} ({self.persona_a.name} vs {self.persona_b.name})"
class DebateMessage(models.Model):
"""A message in a debate, representing a single turn."""
debate = models.ForeignKey(Debate, on_delete=models.CASCADE, related_name='messages')
persona = models.ForeignKey(Persona, on_delete=models.CASCADE)
content = models.TextField()
turn_number = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
# Edge weights for analysis
relevance_score = models.FloatField(default=0.0) # How relevant the message is to the topic
persuasiveness_score = models.FloatField(default=0.0) # How persuasive the argument is
civility_score = models.FloatField(default=0.0) # How civil/respectful the message is
class Meta:
ordering = ['turn_number']
def __str__(self):
return f"Turn {self.turn_number}: {self.persona.name}'s message"
"""API views for the debate system."""
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from .models import Debate, DebateMessage, Persona
from .debate_engine_core import DebateEngineCore
from .debate_graph import DebateGraph
class DebateViewSet(viewsets.ModelViewSet):
"""ViewSet for managing debates."""
def create(self, request):
"""Create a new debate between two personas."""
persona_a_id = request.data.get('persona_a_id')
topic = request.data.get('topic')
if not persona_a_id or not topic:
return Response(
{'error': 'Both persona_a_id and topic are required'},
status=status.HTTP_400_BAD_REQUEST
)
try:
persona_a = Persona.objects.get(id=persona_a_id)
# Create debate
debate = Debate.objects.create(
title=f"Debate on {topic}",
topic=topic,
persona_a=persona_a,
max_turns=10,
is_active=True
)
# Generate opposing persona
engine = DebateEngineCore(debate)
opposing_attrs = engine.generate_opposing_persona(persona_a)
# Create opposing persona
persona_b = Persona.objects.create(
name=f"Opponent of {persona_a.name}",
description=f"Generated opponent for debate on {topic}",
**opposing_attrs
)
debate.persona_b = persona_b
debate.save()
# Create initial message
engine.add_message(
persona_a,
f"Initial statement on topic: {topic}"
)
return Response({
'debate_id': debate.id,
'persona_a': persona_a.id,
'persona_b': persona_b.id
}, status=status.HTTP_201_CREATED)
except Persona.DoesNotExist:
return Response(
{'error': 'Persona not found'},
status=status.HTTP_404_NOT_FOUND
)
@action(detail=True, methods=['post'])
def add_message(self, request, pk=None):
"""Add a message to an existing debate."""
debate = get_object_or_404(Debate, pk=pk)
if not debate.is_active:
return Response(
{'error': 'Debate is no longer active'},
status=status.HTTP_400_BAD_REQUEST
)
persona_id = request.data.get('persona_id')
content = request.data.get('content')
if not persona_id or not content:
return Response(
{'error': 'Both persona_id and content are required'},
status=status.HTTP_400_BAD_REQUEST
)
try:
persona = Persona.objects.get(id=persona_id)
if persona.id not in [debate.persona_a.id, debate.persona_b.id]:
return Response(
{'error': 'Persona is not part of this debate'},
status=status.HTTP_400_BAD_REQUEST
)
engine = DebateEngineCore(debate)
message = engine.add_message(persona, content)
return Response({
'message_id': message.id,
'turn_number': message.turn_number,
'is_debate_active': debate.is_active
})
except Persona.DoesNotExist:
return Response(
{'error': 'Persona not found'},
status=status.HTTP_404_NOT_FOUND
)
@action(detail=True, methods=['get'])
def graph(self, request, pk=None):
"""Get the graph representation of a debate."""
debate = get_object_or_404(Debate, pk=pk)
graph = DebateGraph(debate)
return Response(graph.get_graph_data())
@action(detail=True, methods=['get'])
def strongest_path(self, request, pk=None):
"""Get the strongest path through the debate."""
debate = get_object_or_404(Debate, pk=pk)
weight_type = request.query_params.get('weight_type', 'relevance')
if weight_type not in ['relevance', 'persuasiveness', 'civility']:
return Response(
{'error': 'Invalid weight type'},
status=status.HTTP_400_BAD_REQUEST
)
graph = DebateGraph(debate)
path = graph.get_strongest_path(weight_type)
return Response({
'path': path,
'weight_type': weight_type
})
Here’s a prompt to integrate the given code into the `PersonaGen07` repository:
---
### **Integration Plan for Debate System**
#### **Objective:**
Integrate a fully functional debate system into the `PersonaGen07` repository, leveraging existing persona management functionality to create debates, visualize debates with interactive graphs, and manage user interactions.
---
### **Steps:**
1. **Backend Integration:**
- **Models:** Ensure `Persona` is extended or aligned with debate-specific attributes like `vocabulary_complexity`, `tone`, etc.
- **APIs:**
- Add endpoints for creating debates, posting messages, and fetching graph data into `PersonaGen07` backend using Django REST Framework.
- Integrate the `DebateViewSet` for RESTful operations, ensuring `Persona` relationships are well-mapped in the ORM.
- **Graph Functionality:** Utilize `networkx` for graph-based debate visualizations.
- **Database Migration:** Create migrations for `Debate` and `DebateMessage`.
2. **Frontend Integration:**
- **Add Pages:**
- **Debate List:** Display ongoing and completed debates.
- **Create Debate:** Provide UI to start a new debate by selecting personas.
- **Debate Interface:** Add real-time message exchange and visualization.
- **Graph Visualization:**
- Use `react-force-graph` for interactive visualization.
- Fetch graph data via `/api/debates/{id}/graph` and render using `ForceGraph2D`.
- **Routes:**
- Add new routes to navigate between debates (`/debates/`, `/debates/create`, `/debates/:id`).
3. **UI/UX Enhancements:**
- Use Material UI for consistent and responsive design.
- Provide user feedback with loading indicators, error messages, and tooltips.
4. **Integration Workflow:**
- Add necessary imports for `DebateList`, `CreateDebate`, and `DebateInterface` components in `src/routes`.
- Update `App.tsx` or relevant entry point to include new routes.
- Ensure consistent state management using React hooks.
5. **Testing:**
- Write tests for all API endpoints using Django's `TestCase`.
- Add frontend tests using Jest and React Testing Library to validate UI workflows.
6. **Documentation:**
- Update repository `README.md` with setup instructions.
- Include usage examples for creating and interacting with debates.
---
#### **Required File Changes in PersonaGen07 Repository**
- **Backend:** Update `models.py`, `views.py`, `urls.py`, and `serializers.py` with debate-related logic.
- **Frontend:** Add components for debate creation, interaction, and visualization.
- **API Service:** Extend `src/services/api.ts` to include debate-related endpoints.
---
#### **Outcome:**
The integration will enable users to:
- Create debates using predefined personas.
- Participate in debates with dynamically generated arguments.
- Visualize debate progress using graph-based representation.
---
If you need detailed implementation for a specific file or a walkthrough of any step, let me know!
gpt-4o