Profile gen working
This commit is contained in:
parent
68712fb418
commit
2355c1dfa5
@ -42,13 +42,12 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
||||
if (!view) {
|
||||
return <Box>No user loaded.</Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledPaper sx={sx}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, sm: 2 }} sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minWidth: "80px", maxWidth: "80px" }}>
|
||||
<Avatar
|
||||
src={view.has_profile ? `/api/u/${view.username}/profile/${sessionId}` : ''}
|
||||
src={view.has_profile ? `/api/u/${view.username}/profile/${sessionId}?timestamp=${Date.now()}` : ''}
|
||||
alt={`${view.full_name}'s profile`}
|
||||
sx={{
|
||||
width: 80,
|
||||
|
@ -112,7 +112,8 @@ const streamQueryResponse = (options: StreamQueryOptions) => {
|
||||
try {
|
||||
await processLine(line);
|
||||
} catch (e) {
|
||||
console.error('Error processing line:', line, e);
|
||||
console.error('Error processing line:', e);
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
@ -41,32 +41,47 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
const [streaming, setStreaming] = useState<string>('');
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [user, setUser] = useState<UserInfo>(emptyUser);
|
||||
const controllerRef = useRef<StreamQueryController>(null);
|
||||
const backstoryTextRef = useRef<BackstoryTextFieldRef>(null);
|
||||
const promptRef = useRef<string>(null);
|
||||
const stateRef = useRef<number>(0); /* Generating persona */
|
||||
const userRef = useRef<UserInfo>(user);
|
||||
const [userTimestamp, setUserTimestamp] = useState<number>(0);
|
||||
const [prompt, setPrompt] = useState<string>('');
|
||||
const [resume, setResume] = useState<string>('');
|
||||
const [canGenImage, setCanGenImage] = useState<boolean>(false);
|
||||
const [hasProfile, setHasProfile] = useState<boolean>(false);
|
||||
const [status, setStatus] = useState<string>('');
|
||||
const [timestamp, setTimestamp] = useState<number>(0);
|
||||
const [state, setState] = useState<number>(0); // Replaced stateRef
|
||||
const [shouldGenerateProfile, setShouldGenerateProfile] = useState<boolean>(false);
|
||||
|
||||
// Only keep refs that are truly necessary
|
||||
const controllerRef = useRef<StreamQueryController>(null);
|
||||
const backstoryTextRef = useRef<BackstoryTextFieldRef>(null);
|
||||
|
||||
const generateProfile = useCallback((userInfo?: UserInfo, promptText?: string) => {
|
||||
// Use provided params or current state
|
||||
const currentUser = userInfo || user;
|
||||
const currentPrompt = promptText || prompt;
|
||||
|
||||
const generateProfile = () => {
|
||||
if (controllerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't generate if we still have blank user data
|
||||
if (currentUser.username === "[blank]" || currentUser.first_name === "[blank]") {
|
||||
console.log("Cannot generate profile: user data not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
const imagePrompt = `A photorealistic profile picture of a ${currentUser.age} year old ${currentUser.gender} ${currentUser.ethnicity} person. ${currentPrompt}`
|
||||
setStatus(`Generating: ${imagePrompt}`);
|
||||
setProcessing(true);
|
||||
setCanGenImage(false);
|
||||
stateRef.current = 3;
|
||||
setState(3);
|
||||
const start = Date.now();
|
||||
|
||||
controllerRef.current = streamQueryResponse({
|
||||
query: {
|
||||
prompt: `A photorealistic profile picture of a ${userRef.current.age} year old ${userRef.current.gender} ${userRef.current.ethnicity} person. ${prompt}`,
|
||||
prompt: imagePrompt,
|
||||
agent_options: {
|
||||
username: userRef.current.username,
|
||||
username: currentUser.username,
|
||||
filename: "profile.png"
|
||||
}
|
||||
},
|
||||
@ -81,20 +96,22 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
if (msg.status === "done") {
|
||||
setProcessing(false);
|
||||
controllerRef.current = null;
|
||||
stateRef.current = 0;
|
||||
setState(0);
|
||||
setCanGenImage(true);
|
||||
setHasProfile(true);
|
||||
setShouldGenerateProfile(false);
|
||||
setUser({ ...user, has_profile: true });
|
||||
}
|
||||
break;
|
||||
case "error":
|
||||
console.log(`Error generating persona: ${msg.response} after ${Date.now() - start}`);
|
||||
setSnack(msg.response || "", "error");
|
||||
setProcessing(false);
|
||||
setUser({...userRef.current});
|
||||
controllerRef.current = null;
|
||||
stateRef.current = 0;
|
||||
setState(0);
|
||||
setCanGenImage(true);
|
||||
setHasProfile(true); /* Hack for now */
|
||||
setShouldGenerateProfile(false);
|
||||
break;
|
||||
default:
|
||||
const data = JSON.parse(msg.response || '');
|
||||
@ -113,21 +130,20 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [user, prompt, sessionId, setSnack]);
|
||||
|
||||
};
|
||||
|
||||
const generatePersona = (query: Query) => {
|
||||
const generatePersona = useCallback((query: Query) => {
|
||||
if (controllerRef.current) {
|
||||
return;
|
||||
}
|
||||
setPrompt(query.prompt);
|
||||
promptRef.current = query.prompt;
|
||||
stateRef.current = 0;
|
||||
setState(0);
|
||||
setUser(emptyUser);
|
||||
setStreaming('');
|
||||
setResume('');
|
||||
setProcessing(true);
|
||||
setCanGenImage(false);
|
||||
setShouldGenerateProfile(false); // Reset the flag
|
||||
|
||||
controllerRef.current = streamQueryResponse({
|
||||
query,
|
||||
@ -138,33 +154,35 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
switch (msg.status) {
|
||||
case "partial":
|
||||
case "done":
|
||||
switch (stateRef.current) {
|
||||
case 0: /* Generating persona */
|
||||
let partialUser = JSON.parse(jsonrepair((msg.response || '').trim()));
|
||||
if (!partialUser.full_name) {
|
||||
partialUser.full_name = `${partialUser.first_name} ${partialUser.last_name}`;
|
||||
setState(currentState => {
|
||||
switch (currentState) {
|
||||
case 0: /* Generating persona */
|
||||
let partialUser = JSON.parse(jsonrepair((msg.response || '').trim()));
|
||||
if (!partialUser.full_name) {
|
||||
partialUser.full_name = `${partialUser.first_name} ${partialUser.last_name}`;
|
||||
}
|
||||
console.log("Setting final user data:", partialUser);
|
||||
setUser({ ...partialUser });
|
||||
return 1; /* Generating resume */
|
||||
case 1: /* Generating resume */
|
||||
setResume(msg.response || '');
|
||||
return 2; /* RAG generation */
|
||||
case 2: /* RAG generation */
|
||||
return 3; /* Image generation */
|
||||
default:
|
||||
return currentState;
|
||||
}
|
||||
console.log(partialUser);
|
||||
setUser({...partialUser});
|
||||
stateRef.current++; /* Generating resume */
|
||||
break;
|
||||
case 1: /* Generating resume */
|
||||
stateRef.current++; /* RAG generation */
|
||||
setResume(msg.response || '');
|
||||
break;
|
||||
case 2: /* RAG generation */
|
||||
stateRef.current++; /* Image generation */
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (msg.status === "done") {
|
||||
setProcessing(false);
|
||||
setCanGenImage(true);
|
||||
setStatus('');
|
||||
controllerRef.current = null;
|
||||
stateRef.current = 0;
|
||||
setTimeout(() => {
|
||||
generateProfile();
|
||||
}, 0);
|
||||
setState(0);
|
||||
// Set flag to trigger profile generation after user state updates
|
||||
console.log("Persona generation complete, setting shouldGenerateProfile flag");
|
||||
setShouldGenerateProfile(true);
|
||||
}
|
||||
break;
|
||||
case "thinking":
|
||||
@ -175,9 +193,9 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
console.log(`Error generating persona: ${msg.response}`);
|
||||
setSnack(msg.response || "", "error");
|
||||
setProcessing(false);
|
||||
setUser({...userRef.current});
|
||||
setUser(emptyUser);
|
||||
controllerRef.current = null;
|
||||
stateRef.current = 0;
|
||||
setState(0);
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -185,32 +203,117 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
setStreaming(chunk);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [sessionId, setSnack, generateProfile]);
|
||||
|
||||
const cancelQuery = () => {
|
||||
const cancelQuery = useCallback(() => {
|
||||
if (controllerRef.current) {
|
||||
controllerRef.current.abort();
|
||||
controllerRef.current = null;
|
||||
stateRef.current = 0;
|
||||
setState(0);
|
||||
setProcessing(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onEnter = useCallback((value: string) => {
|
||||
if (processing) {
|
||||
return;
|
||||
}
|
||||
const query: Query = {
|
||||
prompt: value,
|
||||
}
|
||||
generatePersona(query);
|
||||
}, [processing, generatePersona]);
|
||||
|
||||
const handleSendClick = useCallback(() => {
|
||||
const value = (backstoryTextRef.current && backstoryTextRef.current.getAndResetValue()) || "";
|
||||
generatePersona({ prompt: value });
|
||||
}, [generatePersona]);
|
||||
|
||||
// Effect to trigger profile generation when user data is ready
|
||||
useEffect(() => {
|
||||
promptRef.current = prompt;
|
||||
}, [prompt]);
|
||||
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);
|
||||
// Call generateProfile directly with current values to avoid closure issues
|
||||
if (controllerRef.current) {
|
||||
console.log("Controller already active, skipping profile generation");
|
||||
return;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
userRef.current = user;
|
||||
}, [user]);
|
||||
// Don't generate if we still have blank user data
|
||||
if (user.username === "[blank]" || user.first_name === "[blank]") {
|
||||
console.log("Cannot generate profile: user data not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
setCanGenImage(false);
|
||||
setState(3);
|
||||
const start = Date.now();
|
||||
|
||||
controllerRef.current = streamQueryResponse({
|
||||
query: {
|
||||
prompt: `A photorealistic profile picture of a ${user.age} year old ${user.gender} ${user.ethnicity} person. ${prompt}`,
|
||||
agent_options: {
|
||||
username: user.username,
|
||||
filename: "profile.png"
|
||||
}
|
||||
},
|
||||
type: "image",
|
||||
sessionId,
|
||||
connectionBase,
|
||||
onComplete: (msg) => {
|
||||
console.log("Profile generation response:", msg);
|
||||
switch (msg.status) {
|
||||
case "partial":
|
||||
case "done":
|
||||
if (msg.status === "done") {
|
||||
setProcessing(false);
|
||||
controllerRef.current = null;
|
||||
setState(0);
|
||||
setCanGenImage(true);
|
||||
setHasProfile(true);
|
||||
setShouldGenerateProfile(false);
|
||||
}
|
||||
break;
|
||||
case "error":
|
||||
console.log(`Error generating profile: ${msg.response} after ${Date.now() - start}`);
|
||||
setSnack(msg.response || "", "error");
|
||||
setProcessing(false);
|
||||
controllerRef.current = null;
|
||||
setState(0);
|
||||
setCanGenImage(true);
|
||||
setHasProfile(true);
|
||||
setShouldGenerateProfile(false);
|
||||
break;
|
||||
default:
|
||||
const data = JSON.parse(msg.response || '');
|
||||
if (msg.status !== "heartbeat") {
|
||||
console.log(data);
|
||||
}
|
||||
if (data.timestamp) {
|
||||
setTimestamp(data.timestamp);
|
||||
} else {
|
||||
setTimestamp(Date.now())
|
||||
}
|
||||
if (data.message) {
|
||||
setStatus(data.message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [shouldGenerateProfile, user, prompt, sessionId, setSnack]);
|
||||
|
||||
// Handle streaming updates based on current state
|
||||
useEffect(() => {
|
||||
if (streaming.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (stateRef.current) {
|
||||
switch (state) {
|
||||
case 0: /* Generating persona */
|
||||
const partialUser = {...emptyUser, ...JSON.parse(jsonrepair(`${streaming.trim()}...`))};
|
||||
if (!partialUser.full_name) {
|
||||
@ -227,28 +330,23 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Ignore JSON parsing errors during streaming
|
||||
}
|
||||
}, [streaming]);
|
||||
}, [streaming, state]);
|
||||
|
||||
useEffect(() => {
|
||||
setUserTimestamp(Date.now());
|
||||
}, [user]);
|
||||
|
||||
if (!sessionId) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const onEnter = (value: string) => {
|
||||
if (processing) {
|
||||
return;
|
||||
}
|
||||
const query: Query = {
|
||||
prompt: value,
|
||||
}
|
||||
generatePersona(query);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="GenerateCandidate" sx={{
|
||||
display: "flex", flexDirection: "column", flexGrow: 1, gap: 1, width: { xs: '100%', md: '700px', lg: '1024px' }
|
||||
}}>
|
||||
{ user && <CandidateInfo sessionId={sessionId} user={user}/> }
|
||||
{user && <CandidateInfo sessionId={sessionId} user={user} />}
|
||||
{processing &&
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
@ -269,8 +367,8 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
<Box sx={{display: "flex", flexDirection: "column"}}>
|
||||
<Box sx={{ display: "flex", flexDirection: "row", position: "relative" }}>
|
||||
<Avatar
|
||||
src={hasProfile ? `/api/u/${userRef.current.username}/profile/${sessionId}` : ''}
|
||||
alt={`${userRef.current.full_name}'s profile`}
|
||||
src={hasProfile ? `/api/u/${user.username}/profile/${sessionId}` : ''}
|
||||
alt={`${user.full_name}'s profile`}
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
@ -284,7 +382,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||
variant="contained"
|
||||
disabled={sessionId === undefined || processing || !canGenImage}
|
||||
onClick={() => { generateProfile(); }}>
|
||||
onClick={() => { generateProfile() }}>
|
||||
Generate Profile Picture<SendIcon />
|
||||
</Button>
|
||||
</span>
|
||||
@ -306,7 +404,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||
variant="contained"
|
||||
disabled={sessionId === undefined || processing}
|
||||
onClick={() => { generatePersona({ prompt: (backstoryTextRef.current && backstoryTextRef.current.getAndResetValue()) || "" }); }}>
|
||||
onClick={handleSendClick}>
|
||||
Send<SendIcon />
|
||||
</Button>
|
||||
</span>
|
||||
@ -315,7 +413,7 @@ const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
<span style={{ display: "flex" }}> { /* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
||||
<IconButton
|
||||
aria-label="cancel"
|
||||
onClick={() => { cancelQuery(); }}
|
||||
onClick={cancelQuery}
|
||||
sx={{ display: "flex", margin: 'auto 0px' }}
|
||||
size="large"
|
||||
edge="start"
|
||||
|
@ -47,7 +47,7 @@ emptyUser = {
|
||||
}
|
||||
|
||||
generate_persona_system_prompt = """\
|
||||
You are a casing director for a movie. Your job is to provide information on ficticious personas for use in a screen play.
|
||||
You are a casting director for a movie. Your job is to provide information on ficticious personas for use in a screen play.
|
||||
|
||||
All response field MUST BE IN ENGLISH, regardless of ethnicity.
|
||||
|
||||
@ -80,6 +80,8 @@ Provide all information in English ONLY, with no other commentary:
|
||||
```
|
||||
|
||||
Make sure to provide a username and that the field name for the job description is "description".
|
||||
|
||||
DO NOT infer, imply, abbreviate, or state the ethnicity or age in the username or description. You are providing those only for use later by the system when casting individuals for the role.
|
||||
"""
|
||||
|
||||
generate_resume_system_prompt = """
|
||||
@ -109,7 +111,7 @@ 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"]))
|
||||
|
Loading…
x
Reference in New Issue
Block a user