Lots of changes
This commit is contained in:
parent
2a83118689
commit
740ca40c5c
@ -1,20 +1,13 @@
|
||||
import React, { ReactElement, useEffect, useState, useRef, useCallback, createContext, useContext } from 'react';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import { Outlet, useLocation, Routes } from "react-router-dom";
|
||||
import { Box, Container, Paper } from '@mui/material';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import DescriptionIcon from '@mui/icons-material/Description';
|
||||
import QuestionAnswerIcon from '@mui/icons-material/QuestionAnswer';
|
||||
import BarChartIcon from '@mui/icons-material/BarChart';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import BookmarkIcon from '@mui/icons-material/Bookmark';
|
||||
import WorkIcon from '@mui/icons-material/Work';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import BusinessIcon from '@mui/icons-material/Business';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
|
||||
|
||||
@ -22,7 +15,7 @@ import {Header} from './Header';
|
||||
import { Scrollable } from '../../Components/Scrollable';
|
||||
import { Footer } from './Footer';
|
||||
import { Snack, SetSnackType } from '../../Components/Snack';
|
||||
import { UserProvider, useUser, UserInfo } from './UserContext';
|
||||
import { useUser, UserInfo } from './UserContext';
|
||||
import { getBackstoryDynamicRoutes } from './BackstoryRoutes';
|
||||
import { LoadingComponent } from "../Components/LoadingComponent";
|
||||
|
||||
@ -100,9 +93,11 @@ const BackstoryPageContainer = (props : BackstoryPageContainerProps) => {
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
pt: 1,
|
||||
pb: 1,
|
||||
width: "100%",
|
||||
p: { xs: 0, sm: 0.5 }, // Zero padding on mobile (xs), 0.5 on larger screens (sm and up)
|
||||
mt: 0,
|
||||
mb: 0,
|
||||
// width: "100%",
|
||||
maxWidth: { xs: '100%', md: '700px', lg: '1024px' },
|
||||
...sx
|
||||
}}>
|
||||
<Paper
|
||||
@ -110,12 +105,12 @@ const BackstoryPageContainer = (props : BackstoryPageContainerProps) => {
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
p: 3,
|
||||
backgroundColor: 'background.paper',
|
||||
borderRadius: 2,
|
||||
minHeight: '80vh',
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
p: 0.5,
|
||||
backgroundColor: 'background.paper',
|
||||
borderRadius: 0.5,
|
||||
minHeight: '80vh',
|
||||
maxWidth: { xs: '100%', md: '700px', lg: '1024px' },
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
{children}
|
||||
</Paper>
|
||||
@ -165,8 +160,10 @@ const BackstoryLayout: React.FC<{
|
||||
backgroundColor: "#D3CDBF", /* Warm Gray */
|
||||
}}>
|
||||
<Scrollable
|
||||
className="BackstoryPageScrollable"
|
||||
className="BackstoryPageScrollable"
|
||||
sx={{
|
||||
m: 0,
|
||||
p: 0,
|
||||
mt: "72px", /* Needs to be kept in sync with the height of Header if the Header theme changes */
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, Link, Typography, Avatar, Paper, Grid, Chip, SxProps } from '@mui/material';
|
||||
import { Box, Link, Typography, Avatar,Grid, Chip, SxProps } from '@mui/material';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@ -8,20 +8,9 @@ import {
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { UserInfo, useUser } from "./UserContext";
|
||||
import LinkIcon from '@mui/icons-material/Link';
|
||||
import { CopyBubble } from "../../Components/CopyBubble";
|
||||
// Styled components
|
||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: theme.shadows[2],
|
||||
}));
|
||||
|
||||
interface CandidateInfoProps {
|
||||
sessionId: string;
|
||||
@ -37,7 +26,6 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
||||
action = '',
|
||||
sessionId,
|
||||
} = props;
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
@ -72,11 +60,20 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
||||
<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
|
||||
src={candidate.has_profile ? `/api/u/${candidate.username}/profile/${sessionId}?timestamp=${Date.now()}` : ''}
|
||||
alt={`${candidate.full_name}'s profile`}
|
||||
sx={{
|
||||
alignSelf: "flex-start",
|
||||
width: 80,
|
||||
height: 80,
|
||||
border: '2px solid #e0e0e0',
|
||||
@ -85,16 +82,33 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, sm: 10 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
mb: 1 }}>
|
||||
<Box>
|
||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row", alignItems: "center", gap: 1, "& > .MuiTypography-root": { m: 0 } }}>
|
||||
{action !== '' && <Typography variant="body1">{action}</Typography>}
|
||||
<Typography variant="h5" component="h1" sx={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: isMobile ? "column" : "row",
|
||||
alignItems: "left",
|
||||
gap: 1, "& > .MuiTypography-root": { m: 0 }
|
||||
}}>
|
||||
{
|
||||
action !== '' &&
|
||||
<Typography variant="body1">{action}</Typography>
|
||||
}
|
||||
<Typography variant="h5" component="h1"
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
{candidate.full_name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ fontSize: "0.75rem", alignItems: "center" }} >
|
||||
<Link href={`/u/${candidate.username}`}>/u/{candidate.username}</Link>
|
||||
<Link href={`/u/${candidate.username}`}>{`/u/${candidate.username}`}</Link>
|
||||
<CopyBubble
|
||||
onClick={(event: any) => { event.stopPropagation() }}
|
||||
tooltip="Copy link" content={`${window.location.origin}/u/{candidate.username}`} />
|
||||
@ -134,4 +148,4 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
||||
);
|
||||
};
|
||||
|
||||
export { CandidateInfo };
|
||||
export { CandidateInfo };
|
||||
|
@ -30,8 +30,6 @@ const streamQueryResponse = (options: StreamQueryOptions) => {
|
||||
const run = async () => {
|
||||
query.prompt = query.prompt.trim();
|
||||
|
||||
if (!query.prompt) return;
|
||||
|
||||
let data: any = query;
|
||||
if (type === "job_description") {
|
||||
data = {
|
||||
|
@ -1,27 +1,16 @@
|
||||
import React, { forwardRef, useEffect, useState, MouseEventHandler } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Button from '@mui/material/Button';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import MuiMarkdown from 'mui-markdown';
|
||||
|
||||
import { BackstoryPageProps } from '../../Components/BackstoryTab';
|
||||
import { Conversation, ConversationHandle } from '../Components/Conversation';
|
||||
import { ChatQuery, Tunables } from '../../Components/ChatQuery';
|
||||
import { MessageList } from '../../Components/Message';
|
||||
import { CandidateInfo } from 'NewApp/Components/CandidateInfo';
|
||||
import { connectionBase } from '../../Global';
|
||||
import { LoadingComponent } from 'NewApp/Components/LoadingComponent';
|
||||
import { useUser, UserInfo } from "../Components/UserContext";
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { UserInfo } from "../Components/UserContext";
|
||||
|
||||
const CandidateListingPage = (props: BackstoryPageProps) => {
|
||||
const navigate = useNavigate();
|
||||
const { sessionId, setSnack, submitQuery } = props;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const { user } = useUser();
|
||||
const { sessionId, setSnack } = props;
|
||||
const [users, setUsers] = useState<UserInfo[] | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
@ -60,15 +49,15 @@ const CandidateListingPage = (props: BackstoryPageProps) => {
|
||||
};
|
||||
|
||||
fetchUsers();
|
||||
}, [users]);
|
||||
}, [users, sessionId, setSnack]);
|
||||
|
||||
return (
|
||||
<Box sx={{display: "flex", flexDirection: "column"}}>
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Box sx={{ p: 1, textAlign: "center" }}>
|
||||
Not seeing a candidate you like?
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ml: 1}}
|
||||
sx={{m: 1}}
|
||||
onClick={() => { navigate('/generate-candidate')}}>
|
||||
Generate your own perfect AI candidate!
|
||||
</Button>
|
||||
|
@ -10,6 +10,7 @@ import SendIcon from '@mui/icons-material/Send';
|
||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
import { CandidateInfo } from '../Components/CandidateInfo';
|
||||
import { Query } from '../../Components/ChatQuery'
|
||||
import { Quote } from 'NewApp/Components/Quote';
|
||||
import { streamQueryResponse, StreamQueryController } from '../Components/streamQueryResponse';
|
||||
import { connectionBase } from 'Global';
|
||||
import { UserInfo } from '../Components/UserContext';
|
||||
@ -18,8 +19,6 @@ import { BackstoryTextField, BackstoryTextFieldRef } from 'Components/BackstoryT
|
||||
import { jsonrepair } from 'jsonrepair';
|
||||
import { StyledMarkdown } from 'Components/StyledMarkdown';
|
||||
import { Scrollable } from 'Components/Scrollable';
|
||||
import { useForkRef } from '@mui/material';
|
||||
import { BackstoryMessage } from 'Components/Message';
|
||||
import { Pulse } from 'NewApp/Components/Pulse';
|
||||
|
||||
const emptyUser : UserInfo = {
|
||||
@ -44,7 +43,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
const {sessionId, setSnack, submitQuery} = props;
|
||||
const [streaming, setStreaming] = useState<string>('');
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [user, setUser] = useState<UserInfo>(emptyUser);
|
||||
const [user, setUser] = useState<UserInfo | null>(null);
|
||||
const [prompt, setPrompt] = useState<string>('');
|
||||
const [resume, setResume] = useState<string>('');
|
||||
const [canGenImage, setCanGenImage] = useState<boolean>(false);
|
||||
@ -156,8 +155,8 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
|
||||
// Effect to trigger profile generation when user data is ready
|
||||
useEffect(() => {
|
||||
console.log("useEffect triggered - shouldGenerateProfile:", shouldGenerateProfile, "user:", user.username, user.first_name);
|
||||
if (shouldGenerateProfile && user.username !== "[blank]" && user.first_name !== "[blank]") {
|
||||
console.log("useEffect triggered - shouldGenerateProfile:", shouldGenerateProfile, "user:", user?.username, user?.first_name);
|
||||
if (shouldGenerateProfile && user?.username !== "[blank]" && user?.first_name !== "[blank]") {
|
||||
console.log("Triggering profile generation with updated user data:", user);
|
||||
if (controllerRef.current) {
|
||||
console.log("Controller already active, skipping profile generation");
|
||||
@ -165,12 +164,12 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
}
|
||||
|
||||
// Don't generate if we still have blank user data
|
||||
if (user.username === "[blank]" || user.first_name === "[blank]") {
|
||||
if (user?.username === "[blank]" || user?.first_name === "[blank]") {
|
||||
console.log("Cannot generate profile: user data not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
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?.toLocaleLowerCase()} ${user?.ethnicity?.toLocaleLowerCase()} person. ${prompt}`
|
||||
setStatus('Staring image generation...');
|
||||
setProcessing(true);
|
||||
setCanGenImage(false);
|
||||
@ -181,7 +180,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
query: {
|
||||
prompt: imagePrompt,
|
||||
agent_options: {
|
||||
username: user.username,
|
||||
username: user?.username,
|
||||
filename: "profile.png"
|
||||
}
|
||||
},
|
||||
@ -199,7 +198,10 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
setState(0);
|
||||
setCanGenImage(true);
|
||||
setShouldGenerateProfile(false);
|
||||
setUser({ ...user, has_profile: true });
|
||||
setUser({
|
||||
...(user ? user : emptyUser),
|
||||
has_profile: true
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "error":
|
||||
@ -268,7 +270,8 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
gap: 1, width: { xs: '100%', md: '700px', lg: '1024px' }
|
||||
gap: 1,
|
||||
maxWidth: { xs: '100%', md: '700px', lg: '1024px' },
|
||||
}}>
|
||||
{user && <CandidateInfo
|
||||
sessionId={sessionId}
|
||||
@ -278,7 +281,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
{ prompt &&
|
||||
<Box sx={{ display: "flex", flexDirection: "column"}}>
|
||||
<Box sx={{ fontSize: "0.5rem"}}>Persona prompt</Box>
|
||||
<Box sx={{ fontWeight: "bold"}}>{prompt}</Box>
|
||||
<Quote quote={prompt}/>
|
||||
</Box>
|
||||
}
|
||||
{processing &&
|
||||
@ -302,28 +305,35 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
</Box>
|
||||
}
|
||||
<Box sx={{display: "flex", flexDirection: "column"}}>
|
||||
<Box sx={{ display: "flex", flexDirection: "row", position: "relative" }}>
|
||||
<Box sx={{ display: "flex", position: "relative" }}>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
position: "relative"
|
||||
}}>
|
||||
<Box sx={{ display: "flex", position: "relative", width: "min-content", height: "min-content" }}>
|
||||
<Avatar
|
||||
src={user.has_profile ? `/api/u/${user.username}/profile/${sessionId}` : ''}
|
||||
alt={`${user.full_name}'s profile`}
|
||||
src={user?.has_profile ? `/api/u/${user.username}/profile/${sessionId}` : ''}
|
||||
alt={`${user?.full_name}'s profile`}
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
border: '2px solid #e0e0e0',
|
||||
}}
|
||||
/>
|
||||
{processing && <Pulse sx={{ position: "relative", left: "-80px", top: "0px" }} timestamp={timestamp} />}
|
||||
{processing && <Pulse sx={{ position: "relative", left: "-80px", top: "0px", mr: "-80px" }} timestamp={timestamp} />}
|
||||
</Box>
|
||||
<Tooltip title={"Generate Picture"}>
|
||||
<span style={{ display: "flex", flexGrow: 1 }}>
|
||||
<Button
|
||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||
variant="contained"
|
||||
disabled={sessionId === undefined || processing || !canGenImage}
|
||||
|
||||
<Tooltip title={`${user?.has_profile ? 'Re-': ''}Generate Picture`}>
|
||||
<span style={{ display: "flex", flexGrow: 1 }}>
|
||||
<Button
|
||||
sx={{ m: 1, gap: 1, justifySelf: "flex-start", alignSelf: "center", flexGrow: 0, maxHeight: "min-content" }}
|
||||
variant="contained"
|
||||
disabled={
|
||||
sessionId === undefined || processing || !canGenImage
|
||||
}
|
||||
onClick={() => { setShouldGenerateProfile(true); }}>
|
||||
Generate Picture<SendIcon />
|
||||
</Button>
|
||||
{user?.has_profile ? 'Re-': ''}Generate Picture<SendIcon />
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
@ -349,7 +359,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
variant="contained"
|
||||
disabled={sessionId === undefined || processing}
|
||||
onClick={handleSendClick}>
|
||||
Send<SendIcon />
|
||||
Generate New Persona<SendIcon />
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
@ -583,6 +583,10 @@ class WebServer:
|
||||
"full_name": user.full_name,
|
||||
"description": user.description,
|
||||
"contact_info": user.contact_info,
|
||||
"title": user.title,
|
||||
"phone": user.phone,
|
||||
"location": user.location,
|
||||
"email": user.email,
|
||||
"rag_content_size": user.rag_content_size,
|
||||
"has_profile": user.has_profile,
|
||||
"questions": [ q.model_dump(mode='json') for q in user.user_questions],
|
||||
@ -826,6 +830,10 @@ class WebServer:
|
||||
"first_name": user.first_name,
|
||||
"last_name": user.last_name,
|
||||
"full_name": user.full_name,
|
||||
"title": user.title,
|
||||
"phone": user.phone,
|
||||
"location": user.location,
|
||||
"email": user.email,
|
||||
"description": user.description,
|
||||
"contact_info": user.contact_info,
|
||||
"rag_content_size": user.rag_content_size,
|
||||
|
@ -6,6 +6,7 @@ from typing import (
|
||||
ClassVar,
|
||||
cast,
|
||||
Any,
|
||||
Tuple,
|
||||
AsyncGenerator,
|
||||
List,
|
||||
Optional
|
||||
@ -63,6 +64,9 @@ You will be provided with defaults to use if not specified by the user:
|
||||
"age": number,
|
||||
"gender": "male" | "female",
|
||||
"ethnicity": string,
|
||||
"full_name": string,
|
||||
"first_name": string,
|
||||
"last_name": string,
|
||||
}
|
||||
```
|
||||
|
||||
@ -77,8 +81,6 @@ Provide all information in English ONLY, with no other commentary:
|
||||
```json
|
||||
{
|
||||
"username": string, # A likely-to-be unique username, no more than 15 characters (can include numbers and letters but no special characters)
|
||||
"first_name": string,
|
||||
"last_name": string,
|
||||
"description": string, # One to two sentence description of their job
|
||||
"location": string, # In the location, provide ALL of: City, State/Region, and Country
|
||||
"phone": string, # Location appropriate phone number with area code
|
||||
@ -115,9 +117,25 @@ 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.
|
||||
"""
|
||||
|
||||
# Debug version to identify the issue
|
||||
import random
|
||||
import logging
|
||||
|
||||
# Set up logging to see what's happening
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EthnicNameGenerator:
|
||||
def __init__(self):
|
||||
self.nd = NameDataset()
|
||||
try:
|
||||
from names_dataset import NameDataset # type: ignore
|
||||
self.nd = NameDataset()
|
||||
except ImportError:
|
||||
logger.error("NameDataset not available. Please install: pip install names-dataset")
|
||||
self.nd = None
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing NameDataset: {e}")
|
||||
self.nd = None
|
||||
|
||||
# US Census 2020 approximate ethnic distribution
|
||||
self.ethnic_weights = {
|
||||
@ -130,89 +148,136 @@ class EthnicNameGenerator:
|
||||
'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
|
||||
# Map ethnicities to countries (using alpha-2 codes that NameDataset uses)
|
||||
self.ethnic_country_mapping = {
|
||||
'White': ['US', 'GB', 'DE', 'IE', 'IT', 'PL', 'FR', 'CA', 'AU'],
|
||||
'Hispanic': ['MX', 'ES', 'CO', 'PE', 'AR', 'CU', 'VE', 'CL'],
|
||||
'Black': ['US'], # African American names
|
||||
'Asian': ['CN', 'IN', 'PH', 'VN', 'KR', 'JP', 'TH', 'MY'],
|
||||
'Native American': ['US'],
|
||||
'Pacific Islander': ['US'],
|
||||
'Mixed/Other': ['US']
|
||||
}
|
||||
|
||||
def get_weighted_ethnicity(self):
|
||||
def get_weighted_ethnicity(self) -> str:
|
||||
"""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"""
|
||||
def get_names_by_criteria(self, countries: List[str], gender: Optional[str] = None,
|
||||
n: int = 50, use_first_names: bool = True) -> List[str]:
|
||||
"""Get names matching criteria using NameDataset's get_top_names method"""
|
||||
if not self.nd:
|
||||
return []
|
||||
|
||||
all_names = []
|
||||
for country_code in countries:
|
||||
try:
|
||||
# Get top names for this country
|
||||
top_names = self.nd.get_top_names(
|
||||
n=n,
|
||||
use_first_names=use_first_names,
|
||||
country_alpha2=country_code,
|
||||
gender=gender
|
||||
)
|
||||
|
||||
if country_code in top_names:
|
||||
if use_first_names and gender:
|
||||
# For first names with gender specified
|
||||
gender_key = 'M' if gender.upper() in ['M', 'MALE'] else 'F'
|
||||
if gender_key in top_names[country_code]:
|
||||
all_names.extend(top_names[country_code][gender_key])
|
||||
elif use_first_names:
|
||||
# For first names without gender (get both)
|
||||
for gender_names in top_names[country_code].values():
|
||||
all_names.extend(gender_names)
|
||||
else:
|
||||
# For last names
|
||||
all_names.extend(top_names[country_code])
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Error getting names for {country_code}: {e}")
|
||||
continue
|
||||
|
||||
return list(set(all_names)) # Remove duplicates
|
||||
|
||||
def get_name_by_ethnicity(self, ethnicity: str, gender: str = 'random') -> Tuple[str, str, str, str]:
|
||||
"""Generate a name based on ethnicity using the correct NameDataset API"""
|
||||
if gender == 'random':
|
||||
gender = random.choice(['Male', 'Female'])
|
||||
|
||||
countries = self.ethnic_name_mapping.get(ethnicity, ['United States'])
|
||||
countries = self.ethnic_country_mapping.get(ethnicity, ['US'])
|
||||
|
||||
# Try to get names from the mapped countries
|
||||
first_name = None
|
||||
last_name = None
|
||||
# Get first names
|
||||
first_names = self.get_names_by_criteria(
|
||||
countries=countries,
|
||||
gender=gender,
|
||||
use_first_names=True
|
||||
)
|
||||
|
||||
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
|
||||
# Get last names
|
||||
last_names = self.get_names_by_criteria(
|
||||
countries=countries,
|
||||
use_first_names=False
|
||||
)
|
||||
|
||||
# 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'])
|
||||
# Select names or use fallbacks
|
||||
if first_names:
|
||||
first_name = random.choice(first_names)
|
||||
else:
|
||||
first_name = self._get_fallback_first_name(gender, ethnicity)
|
||||
logger.info(f"Using fallback first name for {ethnicity} {gender}")
|
||||
|
||||
# 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'])
|
||||
if last_names:
|
||||
last_name = random.choice(last_names)
|
||||
else:
|
||||
last_name = self._get_fallback_last_name(ethnicity)
|
||||
logger.info(f"Using fallback last name for {ethnicity}")
|
||||
|
||||
return first_name, last_name, ethnicity, gender
|
||||
|
||||
def generate_random_name(self, gender='random'):
|
||||
def _get_fallback_first_name(self, gender: str, ethnicity: str) -> str:
|
||||
"""Provide culturally appropriate fallback first names"""
|
||||
fallback_names = {
|
||||
'White': {
|
||||
'Male': ['James', 'Robert', 'John', 'Michael', 'William', 'David', 'Richard', 'Joseph'],
|
||||
'Female': ['Mary', 'Patricia', 'Jennifer', 'Linda', 'Elizabeth', 'Barbara', 'Susan', 'Jessica']
|
||||
},
|
||||
'Hispanic': {
|
||||
'Male': ['José', 'Luis', 'Miguel', 'Juan', 'Francisco', 'Alejandro', 'Antonio', 'Carlos'],
|
||||
'Female': ['María', 'Guadalupe', 'Juana', 'Margarita', 'Francisca', 'Teresa', 'Rosa', 'Ana']
|
||||
},
|
||||
'Black': {
|
||||
'Male': ['James', 'Robert', 'John', 'Michael', 'William', 'David', 'Richard', 'Charles'],
|
||||
'Female': ['Mary', 'Patricia', 'Linda', 'Elizabeth', 'Barbara', 'Susan', 'Jessica', 'Sarah']
|
||||
},
|
||||
'Asian': {
|
||||
'Male': ['Wei', 'Ming', 'Chen', 'Li', 'Kumar', 'Raj', 'Hiroshi', 'Takeshi'],
|
||||
'Female': ['Mei', 'Lin', 'Ling', 'Priya', 'Yuki', 'Soo', 'Hana', 'Anh']
|
||||
}
|
||||
}
|
||||
|
||||
ethnicity_names = fallback_names.get(ethnicity, fallback_names['White'])
|
||||
return random.choice(ethnicity_names.get(gender, ethnicity_names['Male']))
|
||||
|
||||
def _get_fallback_last_name(self, ethnicity: str) -> str:
|
||||
"""Provide culturally appropriate fallback last names"""
|
||||
fallback_surnames = {
|
||||
'White': ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Wilson', 'Moore'],
|
||||
'Hispanic': ['García', 'Rodríguez', 'Martínez', 'López', 'González', 'Pérez', 'Sánchez', 'Ramírez'],
|
||||
'Black': ['Johnson', 'Williams', 'Brown', 'Jones', 'Davis', 'Miller', 'Wilson', 'Moore'],
|
||||
'Asian': ['Li', 'Wang', 'Zhang', 'Liu', 'Chen', 'Yang', 'Huang', 'Zhao']
|
||||
}
|
||||
|
||||
return random.choice(fallback_surnames.get(ethnicity, fallback_surnames['White']))
|
||||
|
||||
def generate_random_name(self, gender: str = 'random') -> Tuple[str, str, str, str]:
|
||||
"""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'):
|
||||
def generate_multiple_names(self, count: int = 10, gender: str = 'random') -> List[Dict]:
|
||||
"""Generate multiple random names"""
|
||||
names = []
|
||||
for _ in range(count):
|
||||
@ -225,18 +290,19 @@ class EthnicNameGenerator:
|
||||
'gender': actual_gender
|
||||
})
|
||||
return names
|
||||
|
||||
|
||||
class PersonaGenerator(Agent):
|
||||
agent_type: Literal["persona"] = "persona" # type: ignore
|
||||
_agent_type: ClassVar[str] = agent_type # Add this for registration
|
||||
agent_persist: bool = False
|
||||
|
||||
system_prompt: str = generate_persona_system_prompt
|
||||
age: int = Field(default_factory=lambda: random.randint(22, 67))
|
||||
gender: str = Field(default_factory=lambda: random.choice(["male", "female"]))
|
||||
age: int = 0
|
||||
gender: str = ""
|
||||
username: str = ""
|
||||
first_name: str = ""
|
||||
last_name: str = ""
|
||||
full_name: str = ""
|
||||
ethnicity: str = ""
|
||||
|
||||
generator: Any = Field(default=EthnicNameGenerator(), exclude=True)
|
||||
@ -247,6 +313,8 @@ class PersonaGenerator(Agent):
|
||||
self.age = random.randint(22, 67)
|
||||
# Use random.choices with explicit type casting to satisfy Literal type
|
||||
self.first_name, self.last_name, self.ethnicity, self.gender = self.generator.generate_random_name()
|
||||
self.full_name = f"{self.first_name} {self.last_name}"
|
||||
|
||||
|
||||
async def prepare_message(self, message: Message) -> AsyncGenerator[Message, None]:
|
||||
logger.info(f"{self.agent_type} - {inspect.stack()[0].function}")
|
||||
@ -258,18 +326,6 @@ class PersonaGenerator(Agent):
|
||||
message.tunables.enable_rag = False
|
||||
message.tunables.enable_context = False
|
||||
|
||||
self.randomize()
|
||||
|
||||
message.prompt = f"""\
|
||||
```json
|
||||
{json.dumps({
|
||||
"age": self.age,
|
||||
"gender": self.gender,
|
||||
"ethnicity": self.ethnicity
|
||||
})}
|
||||
```
|
||||
{message.prompt}
|
||||
"""
|
||||
message.status = "done"
|
||||
yield message
|
||||
return
|
||||
@ -283,7 +339,6 @@ class PersonaGenerator(Agent):
|
||||
|
||||
self.llm = llm
|
||||
self.model = model
|
||||
original_prompt = message.prompt
|
||||
|
||||
spinner: List[str] = ["\\", "|", "/", "-"]
|
||||
tick: int = 0
|
||||
@ -302,12 +357,33 @@ class PersonaGenerator(Agent):
|
||||
self.context.processing = True
|
||||
|
||||
try:
|
||||
self.randomize()
|
||||
|
||||
original_prompt = message.prompt
|
||||
|
||||
prompt = f"""\
|
||||
```json
|
||||
{json.dumps({
|
||||
"age": self.age,
|
||||
"gender": self.gender,
|
||||
"ethnicity": self.ethnicity,
|
||||
"full_name": self.full_name,
|
||||
"first_name": self.first_name,
|
||||
"last_name": self.last_name,
|
||||
})}
|
||||
```
|
||||
"""
|
||||
|
||||
if original_prompt:
|
||||
prompt += f"""
|
||||
Incorporate the following into the job description: {original_prompt}
|
||||
"""
|
||||
|
||||
#
|
||||
# Generate the persona
|
||||
#
|
||||
async for message in self.call_llm(
|
||||
message=message, system_prompt=self.system_prompt, prompt=original_prompt
|
||||
message=message, system_prompt=self.system_prompt, prompt=prompt
|
||||
):
|
||||
if message.status != "done":
|
||||
yield message
|
||||
@ -316,13 +392,16 @@ class PersonaGenerator(Agent):
|
||||
|
||||
json_str = self.extract_json_from_text(message.response)
|
||||
try:
|
||||
persona = json.loads(json_str) | {
|
||||
"age": self.age,
|
||||
"gender": self.gender,
|
||||
"ethnicity": self.ethnicity
|
||||
}
|
||||
if not persona.get("full_name", None):
|
||||
persona["full_name"] = f"{persona['first_name']} {persona['last_name']}"
|
||||
persona = {
|
||||
"age": self.age,
|
||||
"ethnicity": self.ethnicity,
|
||||
"age": self.age,
|
||||
"gender": self.gender,
|
||||
"ethnicity": self.ethnicity,
|
||||
"full_name": self.full_name,
|
||||
"first_name": self.first_name,
|
||||
"last_name": self.last_name,
|
||||
} | json.loads(json_str)
|
||||
self.username = persona.get("username", None)
|
||||
if not self.username:
|
||||
raise ValueError("LLM did not generate a username")
|
||||
@ -375,6 +454,9 @@ class PersonaGenerator(Agent):
|
||||
}}
|
||||
```
|
||||
"""
|
||||
if original_prompt:
|
||||
prompt += f"""
|
||||
Make sure at least one of the candidate's job descriptions take into account the following: {original_prompt}."""
|
||||
try:
|
||||
async for message in self.call_llm(
|
||||
message=message, system_prompt=generate_resume_system_prompt, prompt=prompt
|
||||
@ -442,13 +524,11 @@ class PersonaGenerator(Agent):
|
||||
logger.error(message.response)
|
||||
message.response = f"Error in persona generation: {str(e)}"
|
||||
logger.error(message.response)
|
||||
self.randomize() # Randomize for next generation
|
||||
yield message
|
||||
return
|
||||
|
||||
# Done processing, add message to conversation
|
||||
self.context.processing = False
|
||||
self.randomize() # Randomize for next generation
|
||||
# Return the final message
|
||||
yield message
|
||||
return
|
||||
@ -461,7 +541,7 @@ class PersonaGenerator(Agent):
|
||||
LLMMessage(role="user", content=prompt),
|
||||
]
|
||||
message.metadata.options = {
|
||||
"seed": 8911,
|
||||
"seed": int(time.time()),
|
||||
"num_ctx": self.context_size,
|
||||
"temperature": temperature, # Higher temperature to encourage tool usage
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ def flux_worker(pipe: Any, params: ImageRequest, status_queue: queue.Queue, task
|
||||
estimated_gen_time = estimates["per_step"] * params.iterations * resolution_scale
|
||||
status_queue.put({
|
||||
"status": "running",
|
||||
"message": f"Starting Flux image generation with {params.iterations} inference steps",
|
||||
"message": f"Initializing image generation...",
|
||||
"estimated_time_remaining": estimated_gen_time,
|
||||
"progress": 0
|
||||
})
|
||||
@ -248,10 +248,10 @@ async def generate_image(message: Message, request: ImageRequest) -> AsyncGenera
|
||||
|
||||
# Initialize or get cached pipeline
|
||||
start_time = time.time()
|
||||
yield status(message, f"Loading {model_type} model: {request.model}")
|
||||
yield status(message, f"Loading generative image model...")
|
||||
pipe = await model_cache.get_pipeline(request.model, device)
|
||||
load_time = time.time() - start_time
|
||||
yield status(message, f"Model loaded in {load_time:.1f} seconds. Generating image with {request.iterations} inference steps", progress=10)
|
||||
yield status(message, f"Model loaded in {load_time:.1f} seconds.", progress=10)
|
||||
|
||||
async for update in async_generate_image(pipe, request):
|
||||
message.status = update.get("status", "thinking")
|
||||
|
@ -41,6 +41,10 @@ class User(BaseModel):
|
||||
description: str = ""
|
||||
rag_content_size : int = 0
|
||||
contact_info : Dict[str, str] = {}
|
||||
title: str = ""
|
||||
phone: str = ""
|
||||
location: str = ""
|
||||
email: str = ""
|
||||
user_questions : List[Question] = []
|
||||
has_profile: bool = False
|
||||
is_ai: bool = False
|
||||
@ -177,6 +181,10 @@ class User(BaseModel):
|
||||
sanitized["username"] = user.get("username", "default")
|
||||
sanitized["first_name"] = user.get("first_name", sanitized["username"])
|
||||
sanitized["last_name"] = user.get("last_name", "")
|
||||
sanitized["title"] = user.get("title", "")
|
||||
sanitized["phone"] = user.get("phone", "")
|
||||
sanitized["location"] = user.get("location", "")
|
||||
sanitized["email"] = user.get("email", "")
|
||||
sanitized["full_name"] = user.get("full_name", f"{sanitized["first_name"]} {sanitized["last_name"]}")
|
||||
sanitized["description"] = user.get("description", "")
|
||||
profile_image = os.path.join(defines.user_dir, sanitized["username"], "profile.png")
|
||||
@ -276,6 +284,10 @@ class User(BaseModel):
|
||||
self.last_name = info.get("last_name", "")
|
||||
self.full_name = info.get("full_name", f"{self.first_name} {self.last_name}")
|
||||
self.description = info.get("description", "")
|
||||
self.title = info.get("title", "")
|
||||
self.phone = info.get("phone", "")
|
||||
self.email = info.get("email", "")
|
||||
self.location = info.get("location", "")
|
||||
self.contact_info = info.get("contact_info", {})
|
||||
profile_image = os.path.join(defines.user_dir, self.username, "profile.png")
|
||||
self.has_profile = os.path.exists(profile_image)
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"first_name": "Eliza",
|
||||
"last_name": "Morgan",
|
||||
"description": "Eliza Morgan is an AI generated persona. In addition, she is a conservation botanist with over a decade of experience in leading ecological restoration projects, managing native plant programs, and advancing rare plant propagation methods across the Pacific Northwest. Her proven record of scientific innovation, effective stakeholder engagement, and successful grant writing are key to her professional strengths.",
|
||||
"questions": [
|
||||
"Is Eliza real?",
|
||||
"What are Eliza's skills?"
|
||||
]
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
# Plant Conservation Specialist
|
||||
|
||||
**Organization:** Oregon Botanical Gardens
|
||||
**Location:** Portland, Oregon
|
||||
**Duration:** April 2017 - May 2020
|
||||
|
||||
## Position Overview
|
||||
As Plant Conservation Specialist at the Oregon Botanical Gardens, I managed the institution's ex-situ conservation program for rare and endangered plant species native to the Pacific Northwest. This position bridged scientific research, hands-on horticulture, and public education.
|
||||
|
||||
## Key Responsibilities
|
||||
|
||||
### Ex-situ Conservation Program
|
||||
- Coordinated conservation collections for 45 rare and endangered plant species
|
||||
- Developed and maintained comprehensive database of accession records, phenology data, and propagation histories
|
||||
- Established genetic management protocols to ensure maximum diversity in conservation collections
|
||||
- Collaborated with Center for Plant Conservation on national rare plant conservation initiatives
|
||||
|
||||
### Propagation & Cultivation
|
||||
- Designed specialized growing environments for challenging species with specific habitat requirements
|
||||
- Experimented with various propagation techniques including tissue culture, specialized seed treatments, and vegetative methods
|
||||
- Maintained detailed documentation of successful and unsuccessful propagation attempts
|
||||
- Achieved first-ever successful cultivation of three critically endangered Oregon wildflowers
|
||||
|
||||
### Reintroduction Planning
|
||||
- Collaborated with federal and state agencies on plant reintroduction strategies
|
||||
- Conducted site assessments to evaluate habitat suitability for reintroductions
|
||||
- Developed monitoring protocols to track survival and reproduction of reintroduced populations
|
||||
- Prepared detailed reintroduction plans for 8 endangered species
|
||||
|
||||
### Research Projects
|
||||
- Designed and implemented germination studies for 15 rare species with unknown propagation requirements
|
||||
- Conducted pollination biology investigations for several endangered plant species
|
||||
- Collaborated with university researchers on seed viability and longevity studies
|
||||
- Maintained comprehensive records of phenological patterns across multiple growing seasons
|
||||
|
||||
### Education & Outreach
|
||||
- Developed educational materials explaining the importance of plant conservation
|
||||
- Led specialized tours focusing on rare plant conservation for visitors and donors
|
||||
- Trained volunteers in proper care of sensitive plant collections
|
||||
- Created interpretive signage for conservation garden displays
|
||||
|
||||
## Notable Projects
|
||||
|
||||
1. **Willamette Valley Prairie Species Recovery**
|
||||
- Established seed bank of 25 declining prairie species
|
||||
- Developed germination protocols that improved propagation success from 30% to 75%
|
||||
- Produced over 5,000 plants for restoration projects throughout the region
|
||||
|
||||
2. **Alpine Rare Plant Conservation Initiative**
|
||||
- Created specialized growing facilities mimicking alpine conditions
|
||||
- Successfully propagated 8 high-elevation rare species never before cultivated
|
||||
- Documented critical temperature and moisture requirements for germination
|
||||
|
||||
3. **Serpentine Soils Conservation Collection**
|
||||
- Developed custom soil mixes replicating challenging serpentine conditions
|
||||
- Maintained living collection of 12 rare serpentine endemic species
|
||||
- Created public display educating visitors about specialized plant adaptations
|
||||
|
||||
## Achievements
|
||||
- Received "Conservation Innovation Award" from the American Public Gardens Association (2019)
|
||||
- Developed propagation protocol for Kincaid's lupine that doubled germination success rates
|
||||
- Established Oregon Botanical Gardens' first dedicated conservation nursery facility
|
||||
- Created seed banking protocols adopted by three other botanical institutions
|
@ -1,75 +0,0 @@
|
||||
# Research Assistant
|
||||
|
||||
**Organization:** Institute for Applied Ecology
|
||||
**Location:** Corvallis, Oregon
|
||||
**Duration:** January 2015 - March 2017
|
||||
|
||||
## Position Overview
|
||||
As Research Assistant at the Institute for Applied Ecology, I supported multiple research projects focused on native plant ecology and restoration techniques. This position provided foundational experience in applying scientific methods to practical conservation challenges.
|
||||
|
||||
## Key Responsibilities
|
||||
|
||||
### Field Surveys
|
||||
- Conducted comprehensive botanical surveys in diverse ecosystems throughout western Oregon
|
||||
- Documented population sizes, health metrics, and habitat conditions for threatened plant species
|
||||
- Established long-term monitoring plots using standardized protocols
|
||||
- Collected voucher specimens for herbarium collections following strict ethical guidelines
|
||||
- Mapped plant populations using GPS and GIS technologies
|
||||
|
||||
### Greenhouse Operations
|
||||
- Assisted with propagation of native plants for restoration experiments and projects
|
||||
- Maintained detailed records of seed treatments, germination rates, and growth parameters
|
||||
- Implemented and monitored experimental growing conditions for research projects
|
||||
- Managed irrigation systems and pest control for approximately 10,000 plants
|
||||
- Prepared plant materials for outplanting at restoration sites
|
||||
|
||||
### Data Collection & Analysis
|
||||
- Collected vegetation data using quadrat, transect, and plot-based sampling methods
|
||||
- Processed and organized large datasets for long-term monitoring studies
|
||||
- Performed statistical analyses using R to assess restoration treatment effectiveness
|
||||
- Created data visualization graphics for reports and publications
|
||||
- Maintained research databases ensuring data quality and accessibility
|
||||
|
||||
### Research Projects
|
||||
- **Prairie Restoration Techniques:**
|
||||
- Compared effectiveness of different site preparation methods on native plant establishment
|
||||
- Monitored post-treatment recovery of native species diversity
|
||||
- Documented invasive species response to various control techniques
|
||||
|
||||
- **Rare Plant Demography:**
|
||||
- Tracked population dynamics of three endangered Willamette Valley plant species
|
||||
- Monitored individual plant survival, growth, and reproductive output
|
||||
- Assessed impacts of management interventions on population trends
|
||||
|
||||
- **Seed Viability Studies:**
|
||||
- Tested germination requirements for 30+ native species
|
||||
- Evaluated effects of smoke, scarification, and stratification on dormancy
|
||||
- Documented optimal storage conditions for maintaining seed viability
|
||||
|
||||
### Publication Support
|
||||
- Co-authored three peer-reviewed publications on prairie restoration techniques
|
||||
- Prepared figures, tables, and data appendices for manuscripts
|
||||
- Conducted literature reviews on specialized ecological topics
|
||||
- Assisted with manuscript revisions based on peer review feedback
|
||||
|
||||
## Key Projects
|
||||
|
||||
1. **Willamette Valley Wet Prairie Restoration**
|
||||
- Implemented experimental plots testing 4 restoration techniques
|
||||
- Collected 3 years of post-treatment vegetation data
|
||||
- Documented successful establishment of 15 target native species
|
||||
|
||||
2. **Endangered Butterfly Habitat Enhancement**
|
||||
- Propagated host and nectar plants for Fender's blue butterfly habitat
|
||||
- Monitored plant-insect interactions in restoration sites
|
||||
- Assessed habitat quality improvements following restoration treatments
|
||||
|
||||
3. **Native Seed Production Research**
|
||||
- Tested cultivation methods for improving seed yields of 10 native species
|
||||
- Documented pollination requirements for optimal seed production
|
||||
- Developed harvest timing recommendations based on seed maturation patterns
|
||||
|
||||
## Publications
|
||||
- Johnson, T., **Morgan, E.**, et al. (2016). "Comparative effectiveness of site preparation techniques for prairie restoration." *Restoration Ecology*, 24(4), 472-481.
|
||||
- Williams, R., **Morgan, E.**, & Smith, B. (2016). "Germination requirements of Willamette Valley wet prairie species." *Native Plants Journal*, 17(2), 99-112.
|
||||
- **Morgan, E.**, Johnson, T., & Davis, A. (2017). "Long-term vegetation response to restoration treatments in degraded oak savanna." *Northwest Science*, 91(1), 27-39.
|
@ -1,55 +0,0 @@
|
||||
# Senior Restoration Botanist
|
||||
|
||||
**Organization:** Pacific Northwest Conservation Alliance
|
||||
**Location:** Portland, Oregon
|
||||
**Duration:** June 2020 - Present
|
||||
|
||||
## Position Overview
|
||||
As Senior Restoration Botanist at the Pacific Northwest Conservation Alliance, I lead complex restoration projects aimed at preserving endangered plant communities throughout the Cascade Range. This role combines technical botanical expertise with project management and leadership responsibilities.
|
||||
|
||||
## Key Responsibilities
|
||||
|
||||
### Project Leadership
|
||||
- Design and implement comprehensive restoration plans for degraded ecosystems with emphasis on rare plant conservation
|
||||
- Lead field operations across multiple concurrent restoration sites covering over 2,000 acres
|
||||
- Establish measurable success criteria and monitoring protocols for all restoration projects
|
||||
- Conduct regular site assessments to track progress and adapt management strategies
|
||||
|
||||
### Native Plant Propagation
|
||||
- Oversee native plant nursery operations producing 75,000+ plants annually
|
||||
- Develop specialized propagation protocols for difficult-to-grow rare species
|
||||
- Maintain detailed records of germination rates, growth metrics, and treatment effects
|
||||
- Coordinate seed collection expeditions throughout diverse ecosystems of the Pacific Northwest
|
||||
|
||||
### Team Management
|
||||
- Supervise a core team of 5 field botanists and up to 12 seasonal restoration technicians
|
||||
- Conduct staff training on plant identification, restoration techniques, and field safety
|
||||
- Facilitate weekly team meetings and monthly progress reviews
|
||||
- Mentor junior staff and provide professional development opportunities
|
||||
|
||||
### Funding & Partnerships
|
||||
- Secured $750,000 in grant funding for riparian habitat restoration projects
|
||||
- Authored major sections of successful proposals to state and federal agencies
|
||||
- Manage project budgets ranging from $50,000 to $250,000
|
||||
- Cultivate partnerships with government agencies, tribes, and conservation NGOs
|
||||
|
||||
### Notable Projects
|
||||
1. **Willamette Valley Prairie Restoration Initiative**
|
||||
- Restored 350 acres of native prairie habitat
|
||||
- Reintroduced 12 threatened plant species with 85% establishment success
|
||||
- Developed innovative seeding techniques that increased native diversity by 40%
|
||||
|
||||
2. **Mount Hood Meadow Rehabilitation**
|
||||
- Led post-wildfire recovery efforts in alpine meadow ecosystems
|
||||
- Implemented erosion control measures using native plant materials
|
||||
- Achieved 90% reduction in invasive species cover within treatment areas
|
||||
|
||||
3. **Columbia River Gorge Rare Plant Recovery**
|
||||
- Established new populations of 5 federally listed plant species
|
||||
- Developed habitat suitability models to identify optimal reintroduction sites
|
||||
- Created monitoring protocols adopted by multiple conservation organizations
|
||||
|
||||
## Achievements
|
||||
- Received Excellence in Ecological Restoration Award from the Society for Ecological Restoration, Northwest Chapter (2023)
|
||||
- Featured in Oregon Public Broadcasting documentary on native plant conservation (2022)
|
||||
- Published 2 peer-reviewed articles on restoration techniques developed during project work
|
Binary file not shown.
@ -1,94 +0,0 @@
|
||||
Eliza Morgan
|
||||
Portland, Oregon | eliza.morgan@nomail.com | (555) 867-5309
|
||||
linkedin.com/in/elizamorgan18383
|
||||
|
||||
**Professional Summary**
|
||||
|
||||
I am a ficticious persona generated by AI to seed the Backstory system for
|
||||
testing, evaluation, and demo purposes.
|
||||
|
||||
Conservation botanist with over a decade of experience leading ecological restoration projects, advancing rare plant propagation methods, and managing native plant programs across the Pacific Northwest. Proven record of scientific innovation, collaborative project leadership, and effective stakeholder engagement. Passionate about preserving botanical diversity through applied research, restoration, and public education.
|
||||
|
||||
**Professional Experience**
|
||||
|
||||
**Senior Restoration Botanist**
|
||||
|
||||
Pacific Northwest Conservation Alliance, Portland, OR | June 2020 – Present
|
||||
|
||||
- Directed restoration efforts across 2,000+ acres of degraded habitat with a focus on endangered plant communities.
|
||||
- Managed propagation and deployment of 75,000+ native plants annually.
|
||||
- Supervised and mentored a cross-functional team of botanists and technicians.
|
||||
- Secured $750,000+ in grant funding and led stakeholder engagement with tribal, governmental, and NGO partners.
|
||||
- Developed seeding and reintroduction techniques increasing native species diversity by 40%.
|
||||
|
||||
* Key Projects & Achievements:
|
||||
|
||||
- Willamette Valley Prairie Restoration: Reintroduced 12 threatened species; 85% establishment success.
|
||||
|
||||
- Mount Hood Meadow Rehabilitation: Reduced invasive cover by 90% post-wildfire.
|
||||
|
||||
- Columbia River Gorge Rare Plant Recovery: Created new populations of 5 federally listed species.
|
||||
|
||||
- Award: Excellence in Ecological Restoration, Society for Ecological Restoration (2023)
|
||||
|
||||
- Media: Featured in OPB documentary on native plant conservation (2022)
|
||||
|
||||
- Publications: 2 peer-reviewed articles on restoration innovations.
|
||||
|
||||
**Plant Conservation Specialist**
|
||||
|
||||
Oregon Botanical Gardens, Portland, OR | April 2017 – May 2020
|
||||
|
||||
- Led ex-situ conservation for 45 endangered Pacific Northwest plant species.
|
||||
- Developed genetic management protocols and created Oregon Botanical’s first dedicated conservation nursery.
|
||||
- Collaborated with the Center for Plant Conservation and state agencies on reintroduction planning.
|
||||
- Authored seed banking protocols adopted by three other institutions.
|
||||
|
||||
* Key Contributions:
|
||||
|
||||
- Propagated 3 species never before cultivated; pioneered tissue culture and seed treatments.
|
||||
|
||||
- Developed Kincaid’s lupine protocol doubling germination success.
|
||||
|
||||
- Designed alpine and serpentine plant displays for public education.
|
||||
|
||||
- Award: Conservation Innovation Award, American Public Gardens Association (2019)
|
||||
|
||||
**Research Assistant**
|
||||
|
||||
Institute for Applied Ecology, Corvallis, OR | January 2015 – March 2017
|
||||
|
||||
- Conducted botanical field surveys, greenhouse propagation, and data analysis for native plant research projects.
|
||||
- Co-authored 3 peer-reviewed studies on prairie restoration, seed viability, and plant demography.
|
||||
- Supported endangered species monitoring and habitat restoration for the Fender’s blue butterfly.
|
||||
|
||||
* Highlighted Projects:
|
||||
|
||||
- Wet Prairie Restoration: Established 15 native species across experimental plots.
|
||||
|
||||
- Seed Viability Studies: Tested dormancy-breaking treatments for 30+ species.
|
||||
|
||||
- Publications:
|
||||
|
||||
- Morgan, E. et al. (2017). \*Northwest Science\*
|
||||
|
||||
- Morgan, E. et al. (2016). \*Restoration Ecology\*, \*Native Plants Journal\*
|
||||
|
||||
**Education**
|
||||
|
||||
B.S. in Botany | Oregon State University, Corvallis, OR | Graduated: 2014
|
||||
|
||||
**Skills & Expertise**
|
||||
|
||||
- Native plant propagation & nursery management
|
||||
- Ecological restoration & reintroduction planning
|
||||
- Grant writing & budget management
|
||||
- GIS & GPS mapping | R statistical analysis
|
||||
- Team leadership & staff mentoring
|
||||
- Science communication & outreach
|
||||
|
||||
**Professional Affiliations**
|
||||
|
||||
- Society for Ecological Restoration (SER)
|
||||
- American Public Gardens Association (APGA)
|
||||
- Center for Plant Conservation (CPC)
|
Loading…
x
Reference in New Issue
Block a user