Lots of changes
This commit is contained in:
parent
e36ed818fb
commit
2a83118689
@ -1,9 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Link, Typography, Avatar, Paper, Grid, Chip, SxProps } from '@mui/material';
|
import { Box, Link, Typography, Avatar, Paper, Grid, Chip, SxProps } from '@mui/material';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardActionArea,
|
||||||
|
Divider,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
import { useTheme } from '@mui/material/styles';
|
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { UserInfo, useUser } from "./UserContext";
|
import { UserInfo, useUser } from "./UserContext";
|
||||||
import LinkIcon from '@mui/icons-material/Link';
|
import LinkIcon from '@mui/icons-material/Link';
|
||||||
import { CopyBubble } from "../../Components/CopyBubble";
|
import { CopyBubble } from "../../Components/CopyBubble";
|
||||||
@ -42,18 +48,34 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
|||||||
if (size < 1000000) return `${(size / 1000).toFixed(1)}K RAG elements`;
|
if (size < 1000000) return `${(size / 1000).toFixed(1)}K RAG elements`;
|
||||||
return `${(size / 1000000).toFixed(1)}M RAG elements`;
|
return `${(size / 1000000).toFixed(1)}M RAG elements`;
|
||||||
};
|
};
|
||||||
const view = props.user || user;
|
const candidate = props.user || user;
|
||||||
|
|
||||||
if (!view) {
|
if (!candidate) {
|
||||||
return <Box>No user loaded.</Box>;
|
return <Box>No user loaded.</Box>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<StyledPaper sx={sx}>
|
<Card
|
||||||
<Grid container spacing={2}>
|
elevation={1}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
borderColor: 'transparent',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
...sx
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardActionArea
|
||||||
|
//onClick={() => setSelectedCandidate(candidate)}
|
||||||
|
sx={{ height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
|
||||||
|
>
|
||||||
|
<CardContent sx={{ flexGrow: 1, p: 3 }}>
|
||||||
|
|
||||||
|
<Grid container spacing={2}>
|
||||||
<Grid size={{ xs: 12, sm: 2 }} sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minWidth: "80px", maxWidth: "80px" }}>
|
<Grid size={{ xs: 12, sm: 2 }} sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minWidth: "80px", maxWidth: "80px" }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={view.has_profile ? `/api/u/${view.username}/profile/${sessionId}?timestamp=${Date.now()}` : ''}
|
src={candidate.has_profile ? `/api/u/${candidate.username}/profile/${sessionId}?timestamp=${Date.now()}` : ''}
|
||||||
alt={`${view.full_name}'s profile`}
|
alt={`${candidate.full_name}'s profile`}
|
||||||
sx={{
|
sx={{
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
@ -68,20 +90,20 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
|||||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row", alignItems: "center", gap: 1, "& > .MuiTypography-root": { m: 0 } }}>
|
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row", alignItems: "center", gap: 1, "& > .MuiTypography-root": { m: 0 } }}>
|
||||||
{action !== '' && <Typography variant="body1">{action}</Typography>}
|
{action !== '' && <Typography variant="body1">{action}</Typography>}
|
||||||
<Typography variant="h5" component="h1" sx={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>
|
<Typography variant="h5" component="h1" sx={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>
|
||||||
{view.full_name}
|
{candidate.full_name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ fontSize: "0.75rem", alignItems: "center" }} >
|
<Box sx={{ fontSize: "0.75rem", alignItems: "center" }} >
|
||||||
<Link href={`/u/${view.username}`}>/u/{view.username}</Link>
|
<Link href={`/u/${candidate.username}`}>/u/{candidate.username}</Link>
|
||||||
<CopyBubble
|
<CopyBubble
|
||||||
onClick={(event: any) => { event.stopPropagation() }}
|
onClick={(event: any) => { event.stopPropagation() }}
|
||||||
tooltip="Copy link" content={`${window.location.origin}/u/{view.username}`} />
|
tooltip="Copy link" content={`${window.location.origin}/u/{candidate.username}`} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{view.rag_content_size !== undefined && view.rag_content_size > 0 && <Chip
|
{candidate.rag_content_size !== undefined && candidate.rag_content_size > 0 && <Chip
|
||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => { navigate('/knowledge-explorer'); event.stopPropagation() }}
|
onClick={(event: React.MouseEvent<HTMLDivElement>) => { navigate('/knowledge-explorer'); event.stopPropagation() }}
|
||||||
label={formatRagSize(view.rag_content_size)}
|
label={formatRagSize(candidate.rag_content_size)}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ ml: 2 }}
|
sx={{ ml: 2 }}
|
||||||
@ -89,11 +111,26 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" color="text.secondary">
|
||||||
{view.description}
|
{candidate.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
<Divider sx={{ my: 2 }} />
|
||||||
|
|
||||||
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||||
|
<strong>Location:</strong> {candidate.location}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||||
|
<strong>Email:</strong> {candidate.email}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<strong>Phone:</strong> {candidate.phone}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</CardContent>
|
||||||
</StyledPaper>
|
</CardActionArea>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ interface UserInfo {
|
|||||||
questions: UserQuestion[],
|
questions: UserQuestion[],
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean,
|
||||||
has_profile: boolean,
|
has_profile: boolean,
|
||||||
|
title: string;
|
||||||
|
location: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
// Fields used in AI generated personas
|
// Fields used in AI generated personas
|
||||||
age?: number,
|
age?: number,
|
||||||
ethnicity?: string,
|
ethnicity?: string,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { forwardRef, useEffect, useState, MouseEventHandler } from 'react';
|
import React, { forwardRef, useEffect, useState, MouseEventHandler } from 'react';
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
@ -62,7 +63,17 @@ const CandidateListingPage = (props: BackstoryPageProps) => {
|
|||||||
}, [users]);
|
}, [users]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box sx={{display: "flex", flexDirection: "column"}}>
|
||||||
|
<Box sx={{ p: 1 }}>
|
||||||
|
Not seeing a candidate you like?
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ml: 1}}
|
||||||
|
onClick={() => { navigate('/generate-candidate')}}>
|
||||||
|
Generate your own perfect AI candidate!
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap"}}>
|
||||||
{users?.map((u, i) =>
|
{users?.map((u, i) =>
|
||||||
<Box key={`${u.username}`}
|
<Box key={`${u.username}`}
|
||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) : void => {
|
onClick={(event: React.MouseEvent<HTMLDivElement>) : void => {
|
||||||
@ -70,10 +81,11 @@ const CandidateListingPage = (props: BackstoryPageProps) => {
|
|||||||
}}
|
}}
|
||||||
sx={{ cursor: "pointer" }}
|
sx={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<CandidateInfo sessionId={sessionId} sx={{ "cursor": "pointer", "&:hover": { border: "2px solid orange" }, border: "2px solid transparent" }} user={u} />
|
<CandidateInfo sessionId={sessionId} sx={{ maxWidth: "320px", "cursor": "pointer", "&:hover": { border: "2px solid orange" }, border: "2px solid transparent" }} user={u} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,7 +33,11 @@ const emptyUser : UserInfo = {
|
|||||||
contact_info: {},
|
contact_info: {},
|
||||||
questions: [],
|
questions: [],
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
has_profile: false
|
has_profile: false,
|
||||||
|
title: '[blank]',
|
||||||
|
location: '[blank]',
|
||||||
|
email: '[blank]',
|
||||||
|
phone: '[blank]',
|
||||||
};
|
};
|
||||||
|
|
||||||
const GenerateCandidate = (props: BackstoryElementProps) => {
|
const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||||
@ -167,7 +171,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imagePrompt = `A photorealistic profile picture of a ${user.age} year old ${user.gender} ${user.ethnicity} person. ${prompt}`
|
const imagePrompt = `A photorealistic profile picture of a ${user.age} year old ${user.gender} ${user.ethnicity} person. ${prompt}`
|
||||||
setStatus(`Generating: ${imagePrompt}`);
|
setStatus('Staring image generation...');
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
setCanGenImage(false);
|
setCanGenImage(false);
|
||||||
setState(3);
|
setState(3);
|
||||||
@ -260,55 +264,76 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="GenerateCandidate" sx={{
|
<Box className="GenerateCandidate" sx={{
|
||||||
display: "flex", flexDirection: "column", flexGrow: 1, gap: 1, width: { xs: '100%', md: '700px', lg: '1024px' }
|
display: "flex",
|
||||||
}}>
|
flexDirection: "column",
|
||||||
{user && <CandidateInfo sessionId={sessionId} user={user} />}
|
flexGrow: 1,
|
||||||
{processing &&
|
gap: 1, width: { xs: '100%', md: '700px', lg: '1024px' }
|
||||||
<Box sx={{
|
}}>
|
||||||
display: "flex",
|
{user && <CandidateInfo
|
||||||
flexDirection: "column",
|
sessionId={sessionId}
|
||||||
alignItems: "center",
|
user={user}
|
||||||
justifyContent: "center",
|
sx={{flexShrink: 1}}/>
|
||||||
m: 2,
|
}
|
||||||
}}>
|
{ prompt &&
|
||||||
<Box sx={{flexDirection: "row", fontWeight: "bold"}}>{status}</Box>
|
<Box sx={{ display: "flex", flexDirection: "column"}}>
|
||||||
<PropagateLoader
|
<Box sx={{ fontSize: "0.5rem"}}>Persona prompt</Box>
|
||||||
size="10px"
|
<Box sx={{ fontWeight: "bold"}}>{prompt}</Box>
|
||||||
loading={processing}
|
|
||||||
aria-label="Loading Spinner"
|
|
||||||
data-testid="loader"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
{processing &&
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
m: 2,
|
||||||
|
}}>
|
||||||
|
{ status && <Box sx={{ display: "flex", flexDirection: "column"}}>
|
||||||
|
<Box sx={{ fontSize: "0.5rem"}}>Generation status</Box>
|
||||||
|
<Box sx={{ fontWeight: "bold"}}>{status}</Box>
|
||||||
|
</Box>}
|
||||||
|
<PropagateLoader
|
||||||
|
size="10px"
|
||||||
|
loading={processing}
|
||||||
|
aria-label="Loading Spinner"
|
||||||
|
data-testid="loader"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
<Box sx={{display: "flex", flexDirection: "column"}}>
|
<Box sx={{display: "flex", flexDirection: "column"}}>
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", position: "relative" }}>
|
<Box sx={{ display: "flex", flexDirection: "row", position: "relative" }}>
|
||||||
<Box sx={{ display: "flex", position: "relative" }}>
|
<Box sx={{ display: "flex", position: "relative" }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.has_profile ? `/api/u/${user.username}/profile/${sessionId}` : ''}
|
src={user.has_profile ? `/api/u/${user.username}/profile/${sessionId}` : ''}
|
||||||
alt={`${user.full_name}'s profile`}
|
alt={`${user.full_name}'s profile`}
|
||||||
sx={{
|
sx={{
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
border: '2px solid #e0e0e0',
|
border: '2px solid #e0e0e0',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{processing && <Pulse sx={{ position: "relative", left: "-80px", top: "0px" }} timestamp={timestamp} />}
|
{processing && <Pulse sx={{ position: "relative", left: "-80px", top: "0px" }} timestamp={timestamp} />}
|
||||||
</Box>
|
</Box>
|
||||||
<Tooltip title={"Generate Picture"}>
|
<Tooltip title={"Generate Picture"}>
|
||||||
<span style={{ display: "flex", flexGrow: 1 }}>
|
<span style={{ display: "flex", flexGrow: 1 }}>
|
||||||
<Button
|
<Button
|
||||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={sessionId === undefined || processing || !canGenImage}
|
disabled={sessionId === undefined || processing || !canGenImage}
|
||||||
onClick={() => { setShouldGenerateProfile(true); }}>
|
onClick={() => { setShouldGenerateProfile(true); }}>
|
||||||
Generate Picture<SendIcon />
|
Generate Picture<SendIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{ resume !== '' && <Paper sx={{pt: 1, pb: 1, pl: 2, pr: 2}}><Scrollable sx={{flexGrow: 1}}><StyledMarkdown {...{content: resume, setSnack, sessionId, submitQuery}}/></Scrollable></Paper> }
|
{ resume !== '' &&
|
||||||
|
<Paper sx={{pt: 1, pb: 1, pl: 2, pr: 2}}>
|
||||||
|
<Scrollable sx={{flexGrow: 1}}>
|
||||||
|
<StyledMarkdown {...{content: resume, setSnack, sessionId, submitQuery}}/>
|
||||||
|
</Scrollable>
|
||||||
|
</Paper> }
|
||||||
<BackstoryTextField
|
<BackstoryTextField
|
||||||
style={{ flexGrow: 0, flexShrink: 1 }}
|
style={{ flexGrow: 0, flexShrink: 1 }}
|
||||||
ref={backstoryTextRef}
|
ref={backstoryTextRef}
|
||||||
@ -343,6 +368,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box sx={{display: "flex", flexGrow: 1}}/>
|
||||||
</Box>);
|
</Box>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import time
|
|||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
|
from names_dataset import NameDataset, NameWrapper # type: ignore
|
||||||
|
|
||||||
from .base import Agent, agent_registry, LLMMessage
|
from .base import Agent, agent_registry, LLMMessage
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
@ -42,6 +44,9 @@ emptyUser = {
|
|||||||
"first_name": "",
|
"first_name": "",
|
||||||
"last_name": "",
|
"last_name": "",
|
||||||
"full_name": "",
|
"full_name": "",
|
||||||
|
"email": "",
|
||||||
|
"phone": "",
|
||||||
|
"title": "",
|
||||||
"contact_info": {},
|
"contact_info": {},
|
||||||
"questions": [],
|
"questions": [],
|
||||||
}
|
}
|
||||||
@ -76,6 +81,9 @@ Provide all information in English ONLY, with no other commentary:
|
|||||||
"last_name": string,
|
"last_name": string,
|
||||||
"description": string, # One to two sentence description of their job
|
"description": string, # One to two sentence description of their job
|
||||||
"location": string, # In the location, provide ALL of: City, State/Region, and Country
|
"location": string, # In the location, provide ALL of: City, State/Region, and Country
|
||||||
|
"phone": string, # Location appropriate phone number with area code
|
||||||
|
"email": string, # primary email address
|
||||||
|
"title": string, # Job title of their current job
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -107,6 +115,117 @@ Use that information to invent a full career resume. Include sections such as:
|
|||||||
Provide the resume in Markdown format. DO NOT provide any commentary before or after the resume.
|
Provide the resume in Markdown format. DO NOT provide any commentary before or after the resume.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class EthnicNameGenerator:
|
||||||
|
def __init__(self):
|
||||||
|
self.nd = NameDataset()
|
||||||
|
|
||||||
|
# US Census 2020 approximate ethnic distribution
|
||||||
|
self.ethnic_weights = {
|
||||||
|
'White': 0.576,
|
||||||
|
'Hispanic': 0.186,
|
||||||
|
'Black': 0.134,
|
||||||
|
'Asian': 0.062,
|
||||||
|
'Native American': 0.013,
|
||||||
|
'Pacific Islander': 0.003,
|
||||||
|
'Mixed/Other': 0.026
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map ethnicities to name origins/countries that approximate those populations
|
||||||
|
self.ethnic_name_mapping = {
|
||||||
|
'White': ['United States', 'United Kingdom', 'Germany', 'Ireland', 'Italy', 'Poland'],
|
||||||
|
'Hispanic': ['Mexico', 'Spain', 'Colombia', 'Peru', 'Argentina', 'Cuba', 'Puerto Rico'],
|
||||||
|
'Black': ['United States'], # African American names
|
||||||
|
'Asian': ['China', 'India', 'Philippines', 'Vietnam', 'Korea', 'Japan'],
|
||||||
|
'Native American': ['United States'], # Limited options in dataset
|
||||||
|
'Pacific Islander': ['United States'], # Limited options in dataset
|
||||||
|
'Mixed/Other': ['United States'] # Default to US names
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_weighted_ethnicity(self):
|
||||||
|
"""Select ethnicity based on US demographic weights"""
|
||||||
|
ethnicities = list(self.ethnic_weights.keys())
|
||||||
|
weights = list(self.ethnic_weights.values())
|
||||||
|
return random.choices(ethnicities, weights=weights)[0]
|
||||||
|
|
||||||
|
def get_name_by_ethnicity(self, ethnicity, gender='random'):
|
||||||
|
"""Generate a name based on ethnicity"""
|
||||||
|
if gender == 'random':
|
||||||
|
gender = random.choice(['Male', 'Female'])
|
||||||
|
|
||||||
|
countries = self.ethnic_name_mapping.get(ethnicity, ['United States'])
|
||||||
|
|
||||||
|
# Try to get names from the mapped countries
|
||||||
|
first_name = None
|
||||||
|
last_name = None
|
||||||
|
|
||||||
|
for country in countries:
|
||||||
|
try:
|
||||||
|
# Get first names
|
||||||
|
if country in self.nd.first_names:
|
||||||
|
country_first_names = self.nd.first_names[country]
|
||||||
|
if gender.lower() in country_first_names:
|
||||||
|
gender_names = country_first_names[gender.lower()]
|
||||||
|
if gender_names:
|
||||||
|
first_name = random.choice(gender_names)
|
||||||
|
break
|
||||||
|
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Fallback to US names if no ethnicity-specific name found
|
||||||
|
if not first_name:
|
||||||
|
try:
|
||||||
|
us_names = self.nd.first_names.get('United States', {})
|
||||||
|
gender_names = us_names.get(gender.lower(), [])
|
||||||
|
if gender_names:
|
||||||
|
first_name = random.choice(gender_names)
|
||||||
|
else:
|
||||||
|
# Ultimate fallback
|
||||||
|
first_name = random.choice(['John', 'Jane', 'Michael', 'Sarah'])
|
||||||
|
except:
|
||||||
|
first_name = random.choice(['John', 'Jane', 'Michael', 'Sarah'])
|
||||||
|
|
||||||
|
# Get last name
|
||||||
|
for country in countries:
|
||||||
|
try:
|
||||||
|
if country in self.nd.last_names and self.nd.last_names[country]:
|
||||||
|
last_name = random.choice(self.nd.last_names[country])
|
||||||
|
break
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Fallback for last name
|
||||||
|
if not last_name:
|
||||||
|
try:
|
||||||
|
us_last_names = self.nd.last_names.get('United States', [])
|
||||||
|
if us_last_names:
|
||||||
|
last_name = random.choice(us_last_names)
|
||||||
|
else:
|
||||||
|
last_name = random.choice(['Smith', 'Johnson', 'Williams', 'Brown'])
|
||||||
|
except:
|
||||||
|
last_name = random.choice(['Smith', 'Johnson', 'Williams', 'Brown'])
|
||||||
|
|
||||||
|
return first_name, last_name, ethnicity, gender
|
||||||
|
|
||||||
|
def generate_random_name(self, gender='random'):
|
||||||
|
"""Generate a random name with ethnicity based on US demographics"""
|
||||||
|
ethnicity = self.get_weighted_ethnicity()
|
||||||
|
return self.get_name_by_ethnicity(ethnicity, gender)
|
||||||
|
|
||||||
|
def generate_multiple_names(self, count=10, gender='random'):
|
||||||
|
"""Generate multiple random names"""
|
||||||
|
names = []
|
||||||
|
for _ in range(count):
|
||||||
|
first, last, ethnicity, actual_gender = self.generate_random_name(gender)
|
||||||
|
names.append({
|
||||||
|
'full_name': f"{first} {last}",
|
||||||
|
'first_name': first,
|
||||||
|
'last_name': last,
|
||||||
|
'ethnicity': ethnicity,
|
||||||
|
'gender': actual_gender
|
||||||
|
})
|
||||||
|
return names
|
||||||
|
|
||||||
class PersonaGenerator(Agent):
|
class PersonaGenerator(Agent):
|
||||||
agent_type: Literal["persona"] = "persona" # type: ignore
|
agent_type: Literal["persona"] = "persona" # type: ignore
|
||||||
_agent_type: ClassVar[str] = agent_type # Add this for registration
|
_agent_type: ClassVar[str] = agent_type # Add this for registration
|
||||||
@ -115,32 +234,19 @@ class PersonaGenerator(Agent):
|
|||||||
system_prompt: str = generate_persona_system_prompt
|
system_prompt: str = generate_persona_system_prompt
|
||||||
age: int = Field(default_factory=lambda: random.randint(22, 67))
|
age: int = Field(default_factory=lambda: random.randint(22, 67))
|
||||||
gender: str = Field(default_factory=lambda: random.choice(["male", "female"]))
|
gender: str = Field(default_factory=lambda: random.choice(["male", "female"]))
|
||||||
ethnicity: Literal[
|
|
||||||
"Asian", "African", "Caucasian", "Hispanic/Latino", "Mixed/Multiracial"
|
|
||||||
] = Field(
|
|
||||||
default_factory=lambda: random.choices(
|
|
||||||
["Asian", "African", "Caucasian", "Hispanic/Latino", "Mixed/Multiracial"],
|
|
||||||
weights=[57.69, 15.38, 19.23, 5.77, 1.92],
|
|
||||||
k=1
|
|
||||||
)[0]
|
|
||||||
)
|
|
||||||
username: str = ""
|
username: str = ""
|
||||||
|
first_name: str = ""
|
||||||
|
last_name: str = ""
|
||||||
|
ethnicity: str = ""
|
||||||
|
|
||||||
|
generator: Any = Field(default=EthnicNameGenerator(), exclude=True)
|
||||||
llm: Any = Field(default=None, exclude=True)
|
llm: Any = Field(default=None, exclude=True)
|
||||||
model: str = Field(default=None, exclude=True)
|
model: str = Field(default=None, exclude=True)
|
||||||
|
|
||||||
def randomize(self):
|
def randomize(self):
|
||||||
self.age = random.randint(22, 67)
|
self.age = random.randint(22, 67)
|
||||||
self.gender = random.choice(["male", "female"])
|
|
||||||
# Use random.choices with explicit type casting to satisfy Literal type
|
# Use random.choices with explicit type casting to satisfy Literal type
|
||||||
self.ethnicity = cast(
|
self.first_name, self.last_name, self.ethnicity, self.gender = self.generator.generate_random_name()
|
||||||
Literal["Asian", "African", "Caucasian", "Hispanic/Latino", "Mixed/Multiracial"],
|
|
||||||
random.choices(
|
|
||||||
["Asian", "African", "Caucasian", "Hispanic/Latino", "Mixed/Multiracial"],
|
|
||||||
weights=[57.69, 15.38, 19.23, 5.77, 1.92],
|
|
||||||
k=1
|
|
||||||
)[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def prepare_message(self, message: Message) -> AsyncGenerator[Message, None]:
|
async def prepare_message(self, message: Message) -> AsyncGenerator[Message, None]:
|
||||||
logger.info(f"{self.agent_type} - {inspect.stack()[0].function}")
|
logger.info(f"{self.agent_type} - {inspect.stack()[0].function}")
|
||||||
@ -152,6 +258,8 @@ class PersonaGenerator(Agent):
|
|||||||
message.tunables.enable_rag = False
|
message.tunables.enable_rag = False
|
||||||
message.tunables.enable_context = False
|
message.tunables.enable_context = False
|
||||||
|
|
||||||
|
self.randomize()
|
||||||
|
|
||||||
message.prompt = f"""\
|
message.prompt = f"""\
|
||||||
```json
|
```json
|
||||||
{json.dumps({
|
{json.dumps({
|
||||||
@ -260,7 +368,10 @@ class PersonaGenerator(Agent):
|
|||||||
"full_name": "{persona["full_name"]}",
|
"full_name": "{persona["full_name"]}",
|
||||||
"location": "{persona["location"]}",
|
"location": "{persona["location"]}",
|
||||||
"age": {persona["age"]},
|
"age": {persona["age"]},
|
||||||
"description": {persona["description"]}
|
"description": {persona["description"]},
|
||||||
|
"title": {persona["title"]},
|
||||||
|
"email": {persona["email"]},
|
||||||
|
"phone": {persona["phone"]}
|
||||||
}}
|
}}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user