Redesign Enhance Brief to auto-apply changes with assumptions modal
- Replace suggestions panel with auto-apply: enhanced text now populates form fields automatically - Add modal showing assumptions made during enhancement (3-5 bullet points) - Reduce prompt scope by 50% for more focused, impactful enhancements - Update backend to return enhanced text + assumptions instead of suggestions array 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
567db4227c
commit
25fcbc826f
4 changed files with 182 additions and 285 deletions
|
|
@ -720,58 +720,62 @@ async def generate_summary_for_persona():
|
|||
@jwt_required()
|
||||
async def enhance_audience_brief_endpoint():
|
||||
"""
|
||||
Generate suggestions to improve an audience brief for better persona generation.
|
||||
|
||||
This endpoint analyzes an audience brief from a behavioral science perspective
|
||||
and provides actionable suggestions to make it more comprehensive and useful
|
||||
for creating detailed, realistic personas.
|
||||
|
||||
Enhance an audience brief and research objective using AI.
|
||||
|
||||
This endpoint analyzes the inputs and returns enhanced versions with
|
||||
additional details woven in, along with a list of assumptions made.
|
||||
|
||||
Request body:
|
||||
{
|
||||
"audience_brief": "A detailed description of the audience context...",
|
||||
"research_objective": "The specific research goals and questions...",
|
||||
"temperature": 0.7 # Optional, controls randomness in generation
|
||||
}
|
||||
|
||||
|
||||
Returns:
|
||||
A JSON object containing separate suggestion arrays for audience_brief and research_objective
|
||||
{
|
||||
"enhanced_audience_brief": "Enhanced text...",
|
||||
"enhanced_research_objective": "Enhanced text...",
|
||||
"assumptions": ["Assumption 1", "Assumption 2", ...]
|
||||
}
|
||||
"""
|
||||
user_id = get_jwt_identity()
|
||||
data = await request.get_json() or {}
|
||||
|
||||
|
||||
# Extract parameters
|
||||
audience_brief = data.get('audience_brief')
|
||||
research_objective = data.get('research_objective')
|
||||
|
||||
|
||||
if not audience_brief:
|
||||
return jsonify({"error": "Missing audience brief", "message": "Audience brief is required"}), 400
|
||||
if not research_objective:
|
||||
return jsonify({"error": "Missing research objective", "message": "Research objective is required"}), 400
|
||||
|
||||
|
||||
# Validate both fields have minimum length (10 characters each as per requirements)
|
||||
if len(audience_brief.strip()) < 10:
|
||||
return jsonify({"error": "Audience brief too short", "message": "Audience brief must be at least 10 characters long"}), 400
|
||||
if len(research_objective.strip()) < 10:
|
||||
return jsonify({"error": "Research objective too short", "message": "Research objective must be at least 10 characters long"}), 400
|
||||
|
||||
|
||||
temperature = data.get('temperature', 1.0)
|
||||
if not (0 <= temperature <= 1.5):
|
||||
temperature = 1.0
|
||||
|
||||
|
||||
try:
|
||||
# Generate enhancement suggestions
|
||||
suggestions = await enhance_audience_brief(
|
||||
# Generate enhanced content
|
||||
enhancement_result = await enhance_audience_brief(
|
||||
audience_brief=audience_brief.strip(),
|
||||
research_objective=research_objective.strip(),
|
||||
temperature=temperature
|
||||
)
|
||||
|
||||
total_count = len(suggestions['audience_brief']) + len(suggestions['research_objective'])
|
||||
|
||||
return jsonify({
|
||||
"message": f"Successfully generated {total_count} enhancement suggestions",
|
||||
"suggestions": suggestions
|
||||
"message": "Successfully enhanced brief",
|
||||
"enhanced_audience_brief": enhancement_result['enhanced_audience_brief'],
|
||||
"enhanced_research_objective": enhancement_result['enhanced_research_objective'],
|
||||
"assumptions": enhancement_result['assumptions']
|
||||
}), 200
|
||||
|
||||
|
||||
except PersonaGenerationError as e:
|
||||
current_app.logger.error(f"Audience brief enhancement error: {str(e)}")
|
||||
return jsonify({"error": "Failed to enhance audience brief", "message": str(e)}), 500
|
||||
|
|
|
|||
|
|
@ -814,18 +814,21 @@ async def enhance_audience_brief(
|
|||
audience_brief: str,
|
||||
research_objective: str,
|
||||
temperature: float = 1.0
|
||||
) -> Dict[str, List[str]]:
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate suggestions to improve both audience brief and research objective for better persona generation.
|
||||
|
||||
Enhance audience brief and research objective with AI-generated improvements.
|
||||
|
||||
Args:
|
||||
audience_brief: The audience brief to analyze and improve
|
||||
research_objective: The research objective to analyze and improve
|
||||
audience_brief: The audience brief to enhance
|
||||
research_objective: The research objective to enhance
|
||||
temperature: Controls randomness in generation (0.0 = deterministic, 1.0 = creative)
|
||||
|
||||
|
||||
Returns:
|
||||
A dictionary with separate suggestion arrays for 'audience_brief' and 'research_objective'
|
||||
|
||||
A dictionary with:
|
||||
- 'enhanced_audience_brief': The enhanced audience brief text
|
||||
- 'enhanced_research_objective': The enhanced research objective text
|
||||
- 'assumptions': List of assumptions/additions made
|
||||
|
||||
Raises:
|
||||
PersonaGenerationError: If there's an issue with the AI generation or JSON parsing
|
||||
"""
|
||||
|
|
@ -838,103 +841,74 @@ async def enhance_audience_brief(
|
|||
})
|
||||
except PromptLoaderError as e:
|
||||
raise PersonaGenerationError(f"Error loading enhancement prompt: {str(e)}")
|
||||
|
||||
# Generate suggestions using the LLM service
|
||||
|
||||
# Generate enhanced content using the LLM service
|
||||
try:
|
||||
raw_response = await LLMService.generate_content(
|
||||
prompt=final_prompt,
|
||||
temperature=temperature
|
||||
)
|
||||
|
||||
# DEBUG: Log the raw response from the LLM
|
||||
print(f"DEBUG: Raw LLM response: {raw_response[:500]}...")
|
||||
|
||||
|
||||
# Clean up the response for proper JSON parsing
|
||||
clean_response = raw_response.strip()
|
||||
|
||||
# DEBUG: Log the cleaned response
|
||||
print(f"DEBUG: Cleaned response: {clean_response[:500]}...")
|
||||
|
||||
|
||||
# Remove markdown code blocks if present
|
||||
if clean_response.startswith("```json"):
|
||||
clean_response = clean_response.strip("```json").strip("```").strip()
|
||||
elif clean_response.startswith("```"):
|
||||
clean_response = clean_response.strip("```").strip()
|
||||
|
||||
|
||||
# Try to find the JSON object in the response if there's extra text
|
||||
if not clean_response.startswith("{"):
|
||||
# Look for the opening brace
|
||||
start_idx = clean_response.find("{")
|
||||
if start_idx != -1:
|
||||
# Find the matching closing brace
|
||||
end_idx = clean_response.rfind("}")
|
||||
if end_idx != -1 and end_idx > start_idx:
|
||||
clean_response = clean_response[start_idx:end_idx+1]
|
||||
|
||||
|
||||
# Parse the JSON response
|
||||
try:
|
||||
suggestions_object = json.loads(clean_response)
|
||||
|
||||
# DEBUG: Log the entire parsed response for troubleshooting
|
||||
print(f"DEBUG: Parsed suggestions object: {json.dumps(suggestions_object, indent=2)}")
|
||||
|
||||
enhancement_result = json.loads(clean_response)
|
||||
|
||||
# Verify it's an object
|
||||
if not isinstance(suggestions_object, dict):
|
||||
raise PersonaGenerationError(f"Expected a JSON object with suggestions but got {type(suggestions_object)}")
|
||||
|
||||
if not isinstance(enhancement_result, dict):
|
||||
raise PersonaGenerationError(f"Expected a JSON object but got {type(enhancement_result)}")
|
||||
|
||||
# Verify required keys exist
|
||||
if 'audience_brief' not in suggestions_object or 'research_objective' not in suggestions_object:
|
||||
raise PersonaGenerationError("Response must contain both 'audience_brief' and 'research_objective' keys")
|
||||
|
||||
# Verify both values are arrays
|
||||
if not isinstance(suggestions_object['audience_brief'], list):
|
||||
raise PersonaGenerationError(f"audience_brief must be an array but got {type(suggestions_object['audience_brief'])}")
|
||||
if not isinstance(suggestions_object['research_objective'], list):
|
||||
raise PersonaGenerationError(f"research_objective must be an array but got {type(suggestions_object['research_objective'])}")
|
||||
|
||||
# Verify all items in both arrays are strings - with detailed debugging and auto-fixing
|
||||
for i, suggestion in enumerate(suggestions_object['audience_brief']):
|
||||
if not isinstance(suggestion, str):
|
||||
print(f"DEBUG: audience_brief[{i}] type: {type(suggestion)}, value: {suggestion}")
|
||||
# Try to convert to string if it's a dict with a text field, otherwise stringify
|
||||
if isinstance(suggestion, dict) and 'text' in suggestion:
|
||||
suggestions_object['audience_brief'][i] = suggestion['text']
|
||||
print(f"DEBUG: Fixed audience_brief[{i}] by extracting 'text' field: {suggestion['text']}")
|
||||
elif isinstance(suggestion, dict):
|
||||
# Convert dict to string representation
|
||||
suggestions_object['audience_brief'][i] = str(suggestion)
|
||||
print(f"DEBUG: Fixed audience_brief[{i}] by converting dict to string")
|
||||
else:
|
||||
suggestions_object['audience_brief'][i] = str(suggestion)
|
||||
print(f"DEBUG: Fixed audience_brief[{i}] by converting to string")
|
||||
|
||||
for i, suggestion in enumerate(suggestions_object['research_objective']):
|
||||
if not isinstance(suggestion, str):
|
||||
print(f"DEBUG: research_objective[{i}] type: {type(suggestion)}, value: {suggestion}")
|
||||
# Try to convert to string if it's a dict with a text field, otherwise stringify
|
||||
if isinstance(suggestion, dict) and 'text' in suggestion:
|
||||
suggestions_object['research_objective'][i] = suggestion['text']
|
||||
print(f"DEBUG: Fixed research_objective[{i}] by extracting 'text' field: {suggestion['text']}")
|
||||
elif isinstance(suggestion, dict):
|
||||
# Convert dict to string representation
|
||||
suggestions_object['research_objective'][i] = str(suggestion)
|
||||
print(f"DEBUG: Fixed research_objective[{i}] by converting dict to string")
|
||||
else:
|
||||
suggestions_object['research_objective'][i] = str(suggestion)
|
||||
print(f"DEBUG: Fixed research_objective[{i}] by converting to string")
|
||||
|
||||
required_keys = ['enhanced_audience_brief', 'enhanced_research_objective', 'assumptions']
|
||||
for key in required_keys:
|
||||
if key not in enhancement_result:
|
||||
raise PersonaGenerationError(f"Response missing required key: '{key}'")
|
||||
|
||||
# Verify enhanced texts are strings
|
||||
if not isinstance(enhancement_result['enhanced_audience_brief'], str):
|
||||
raise PersonaGenerationError("enhanced_audience_brief must be a string")
|
||||
if not isinstance(enhancement_result['enhanced_research_objective'], str):
|
||||
raise PersonaGenerationError("enhanced_research_objective must be a string")
|
||||
|
||||
# Verify assumptions is a list
|
||||
if not isinstance(enhancement_result['assumptions'], list):
|
||||
raise PersonaGenerationError("assumptions must be an array")
|
||||
|
||||
# Convert any non-string assumptions to strings
|
||||
for i, assumption in enumerate(enhancement_result['assumptions']):
|
||||
if not isinstance(assumption, str):
|
||||
enhancement_result['assumptions'][i] = str(assumption)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
raise PersonaGenerationError(f"Failed to parse JSON response: {str(e)}. Raw response: {clean_response[:200]}...")
|
||||
|
||||
|
||||
except LLMServiceError as e:
|
||||
raise PersonaGenerationError(f"Error from LLM service: {str(e)}")
|
||||
|
||||
# Validate we got at least one suggestion in each field
|
||||
if len(suggestions_object['audience_brief']) == 0 and len(suggestions_object['research_objective']) == 0:
|
||||
raise PersonaGenerationError("No suggestions were generated for either field")
|
||||
|
||||
return suggestions_object
|
||||
|
||||
|
||||
# Validate we got meaningful content
|
||||
if not enhancement_result['enhanced_audience_brief'].strip():
|
||||
raise PersonaGenerationError("Enhanced audience brief is empty")
|
||||
if not enhancement_result['enhanced_research_objective'].strip():
|
||||
raise PersonaGenerationError("Enhanced research objective is empty")
|
||||
|
||||
return enhancement_result
|
||||
|
||||
except Exception as e:
|
||||
if isinstance(e, PersonaGenerationError):
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
You are an expert in behavioral science and focus group persona recruitment with over 15 years of experience in market research and consumer psychology. Your role is to analyze both audience briefs and research objectives to provide actionable suggestions for improving them for more effective persona generation.
|
||||
You are an expert in behavioral science and focus group persona recruitment with over 15 years of experience in market research and consumer psychology. Your role is to enhance audience briefs and research objectives to make them more comprehensive for persona generation.
|
||||
|
||||
Given the audience brief and research objective below, analyze each field separately and provide specific, actionable suggestions to make them more comprehensive and useful for creating detailed, realistic personas. Focus on identifying gaps and opportunities for enhancement in each area.
|
||||
Given the audience brief and research objective below, enhance each field by adding specific, actionable details that will improve persona generation quality. Preserve the user's original intent and text while weaving in targeted enhancements.
|
||||
|
||||
Audience Brief:
|
||||
{audience_brief}
|
||||
|
|
@ -8,68 +8,42 @@ Audience Brief:
|
|||
Research Objective:
|
||||
{research_objective}
|
||||
|
||||
Your suggestions should help the researcher create more detailed and accurate personas by addressing potential gaps in both the audience definition and research focus. For each field, consider these areas for improvement:
|
||||
## Your Task
|
||||
|
||||
**For Audience Brief - Demographics & Segmentation:**
|
||||
- Age ranges or generational characteristics (Gen Z, Millennials, Gen X, Boomers)
|
||||
- Income levels, budget considerations, or socioeconomic factors
|
||||
- Geographic specificity (urban vs. rural, specific regions, cultural contexts)
|
||||
- Education levels and professional backgrounds
|
||||
- Family status and household composition
|
||||
Enhance both fields by adding 3-5 key details across these priority areas:
|
||||
|
||||
**For Audience Brief - Behavioral Patterns:**
|
||||
- Technology usage patterns and digital behavior
|
||||
- Shopping and consumption habits
|
||||
- Communication preferences and media consumption
|
||||
- Lifestyle patterns and daily routines
|
||||
- Decision-making processes and influences
|
||||
**For Audience Brief** - Add specifics about:
|
||||
- Demographics (age range, income level, geographic focus)
|
||||
- Behavioral patterns (technology usage, shopping habits)
|
||||
- Psychographics (values, motivations, lifestyle)
|
||||
|
||||
**For Audience Brief - Psychographic Insights:**
|
||||
- Values, beliefs, and attitudes
|
||||
- Motivations and pain points
|
||||
- Aspirations and goals
|
||||
- Personality traits and behavioral tendencies
|
||||
- Social and cultural influences
|
||||
**For Research Objective** - Add specifics about:
|
||||
- Research focus (specific questions or behaviors to explore)
|
||||
- User journey stage (awareness, consideration, purchase, retention)
|
||||
- Success metrics or outcomes
|
||||
|
||||
**For Research Objective - Research Focus & Scope:**
|
||||
- Specific research questions or hypotheses to be tested
|
||||
- Key behaviors, attitudes, or perceptions to explore
|
||||
- Product or service interaction points and touchpoints
|
||||
- User journey stages or experience considerations
|
||||
- Measurable outcomes or success metrics
|
||||
## Guidelines
|
||||
|
||||
**For Research Objective - Research Design & Methodology:**
|
||||
- Clarity on what type of insights are needed (qualitative vs quantitative)
|
||||
- Specific scenarios or contexts to explore
|
||||
- Decision-making factors or influencing variables
|
||||
- Comparison points or competitive analysis needs
|
||||
- Actionable business questions the research should answer
|
||||
1. Keep the user's original text intact - enhance, don't replace
|
||||
2. Make enhancements natural and seamlessly integrated
|
||||
3. Be specific and actionable (e.g., "ages 25-45" not "various ages")
|
||||
4. Focus on the most impactful 3-5 additions total
|
||||
5. Assumptions should be reasonable based on context provided
|
||||
|
||||
**For Both Fields - Context & Environment:**
|
||||
- Industry or market context
|
||||
- Competitive landscape considerations
|
||||
- Seasonal or temporal factors
|
||||
- Regulatory or compliance considerations
|
||||
- Environmental or sustainability factors
|
||||
## Output Format
|
||||
|
||||
Analyze each field separately and provide suggestions for improving both the audience brief and research objective.
|
||||
Return ONLY a JSON object with this exact structure:
|
||||
|
||||
Format your response as a structured JSON object with separate arrays for each field. Each suggestion should be a complete, actionable sentence that:
|
||||
- Is specific and concrete (not generic advice)
|
||||
- Explains WHY the addition would improve persona quality
|
||||
- Suggests specific questions or details to consider
|
||||
- Is directly relevant to the provided content
|
||||
|
||||
Example format:
|
||||
{
|
||||
"audience_brief": [
|
||||
"Consider specifying age ranges or generational characteristics, as different generations have distinct digital behaviors and communication preferences that would significantly impact persona authenticity",
|
||||
"Have you thought about income levels or budget considerations? Understanding economic constraints and spending power would help create more realistic purchasing behaviors and decision-making patterns"
|
||||
],
|
||||
"research_objective": [
|
||||
"Define specific measurable outcomes or success metrics for the research, as this will help create personas with clear behavioral indicators and decision-making patterns",
|
||||
"Consider specifying which stage of the user journey you want to focus on (awareness, consideration, purchase, retention), as this will influence the types of motivations and pain points personas should exhibit"
|
||||
"enhanced_audience_brief": "The original brief text, enhanced with additional specific details woven in naturally...",
|
||||
"enhanced_research_objective": "The original objective text, enhanced with additional specific details woven in naturally...",
|
||||
"assumptions": [
|
||||
"Added age range: 25-45 based on [reasoning]",
|
||||
"Specified geographic focus: urban areas",
|
||||
"Included income level: middle to upper-middle class"
|
||||
]
|
||||
}
|
||||
|
||||
Return ONLY the JSON object with no additional text, explanations, or markdown formatting.
|
||||
The assumptions array should contain 3-5 brief bullet points explaining what was added and why. Each assumption should be a single concise sentence.
|
||||
|
||||
Return ONLY the JSON object with no additional text, explanations, or markdown formatting.
|
||||
|
|
|
|||
|
|
@ -3,10 +3,18 @@ import { useState } from 'react';
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { Upload, Users, FileText, RefreshCw, CheckCircle2, UploadCloud, Lightbulb, X, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { Users, FileText, RefreshCw, CheckCircle2, Lightbulb, Sparkles } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { aiPersonasApi } from '@/lib/api';
|
||||
import AssetUploader from '@/components/AssetUploader';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -53,12 +61,10 @@ interface AIRecruiterFormProps {
|
|||
}
|
||||
|
||||
export default function AIRecruiterForm({ onSubmit, isGenerating }: AIRecruiterFormProps) {
|
||||
// State for suggestions feature
|
||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||
const [suggestionsCollapsed, setSuggestionsCollapsed] = useState(false);
|
||||
const [suggestions, setSuggestions] = useState<{audience_brief: string[], research_objective: string[]}>({audience_brief: [], research_objective: []});
|
||||
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
|
||||
const [enhancementError, setEnhancementError] = useState<string | null>(null);
|
||||
// State for enhance brief feature
|
||||
const [isEnhancing, setIsEnhancing] = useState(false);
|
||||
const [showAssumptionsModal, setShowAssumptionsModal] = useState(false);
|
||||
const [assumptions, setAssumptions] = useState<string[]>([]);
|
||||
const [uploadedFiles, setUploadedFiles] = useState<FileList | null>(null);
|
||||
const [currentFiles, setCurrentFiles] = useState<File[]>([]);
|
||||
|
||||
|
|
@ -88,14 +94,14 @@ export default function AIRecruiterForm({ onSubmit, isGenerating }: AIRecruiterF
|
|||
const handleEnhanceBrief = async () => {
|
||||
const briefText = audienceBriefValue?.trim();
|
||||
const objectiveText = researchObjectiveValue?.trim();
|
||||
|
||||
|
||||
if (!briefText || briefText.length < 10) {
|
||||
toast.error("Audience brief too short", {
|
||||
description: "Please enter at least 10 characters in the audience brief"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!objectiveText || objectiveText.length < 10) {
|
||||
toast.error("Research objective too short", {
|
||||
description: "Please enter at least 10 characters in the research objective"
|
||||
|
|
@ -103,60 +109,56 @@ export default function AIRecruiterForm({ onSubmit, isGenerating }: AIRecruiterF
|
|||
return;
|
||||
}
|
||||
|
||||
setIsLoadingSuggestions(true);
|
||||
setEnhancementError(null);
|
||||
setIsEnhancing(true);
|
||||
|
||||
try {
|
||||
const response = await aiPersonasApi.enhanceAudienceBrief(briefText, objectiveText);
|
||||
setSuggestions(response.data.suggestions || {audience_brief: [], research_objective: []});
|
||||
setShowSuggestions(true);
|
||||
setSuggestionsCollapsed(false); // Auto-expand when new suggestions load
|
||||
|
||||
const totalCount = (response.data.suggestions?.audience_brief?.length || 0) + (response.data.suggestions?.research_objective?.length || 0);
|
||||
toast.success("Enhancement suggestions generated", {
|
||||
description: `Generated ${totalCount} suggestions to improve your research inputs`
|
||||
|
||||
// Auto-apply the enhanced text to the form fields
|
||||
form.setValue('audienceBrief', response.data.enhanced_audience_brief);
|
||||
form.setValue('researchObjective', response.data.enhanced_research_objective);
|
||||
|
||||
// Store assumptions and show modal
|
||||
setAssumptions(response.data.assumptions || []);
|
||||
setShowAssumptionsModal(true);
|
||||
|
||||
toast.success("Brief enhanced successfully", {
|
||||
description: "Your inputs have been updated with additional details"
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error("Error enhancing audience brief:", error);
|
||||
|
||||
console.error("Error enhancing brief:", error);
|
||||
|
||||
let errorMessage = "Please try again or modify your brief";
|
||||
let errorTitle = "Failed to generate suggestions";
|
||||
|
||||
// Handle specific error types
|
||||
let errorTitle = "Failed to enhance brief";
|
||||
|
||||
if (error && typeof error === 'object') {
|
||||
const apiError = error as { code?: string; message?: string; response?: { status?: number; data?: { message?: string } } };
|
||||
|
||||
|
||||
if (apiError.code === "ECONNABORTED" || apiError.message?.includes("timeout")) {
|
||||
errorTitle = "Request timeout";
|
||||
errorMessage = "The AI took too long to analyze your brief. Please try again.";
|
||||
errorMessage = "The AI took too long to enhance your brief. Please try again.";
|
||||
} else if (apiError.response?.status === 500) {
|
||||
errorTitle = "Server error";
|
||||
errorMessage = apiError.response?.data?.message || "The server encountered an error. Please try again later.";
|
||||
} else if (apiError.response?.status === 400) {
|
||||
errorTitle = "Invalid brief";
|
||||
errorMessage = apiError.response?.data?.message || "Please check your audience brief and try again.";
|
||||
errorTitle = "Invalid input";
|
||||
errorMessage = apiError.response?.data?.message || "Please check your inputs and try again.";
|
||||
} else if (apiError.message) {
|
||||
errorMessage = apiError.message;
|
||||
}
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
setEnhancementError(errorMessage);
|
||||
|
||||
toast.error(errorTitle, {
|
||||
description: errorMessage,
|
||||
duration: 5000
|
||||
});
|
||||
} finally {
|
||||
setIsLoadingSuggestions(false);
|
||||
setIsEnhancing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle toggling suggestions collapse/expand
|
||||
const handleToggleCollapse = () => {
|
||||
setSuggestionsCollapsed(!suggestionsCollapsed);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
|
|
@ -208,16 +210,16 @@ export default function AIRecruiterForm({ onSubmit, isGenerating }: AIRecruiterF
|
|||
<div className="space-y-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleEnhanceBrief}
|
||||
disabled={!audienceBriefValue || audienceBriefValue.trim().length < 10 || !researchObjectiveValue || researchObjectiveValue.trim().length < 10 || isLoadingSuggestions || isGenerating}
|
||||
disabled={!audienceBriefValue || audienceBriefValue.trim().length < 10 || !researchObjectiveValue || researchObjectiveValue.trim().length < 10 || isEnhancing || isGenerating}
|
||||
className="flex items-center gap-2 hover-transition"
|
||||
>
|
||||
{isLoadingSuggestions ? (
|
||||
{isEnhancing ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
Analyzing Research Inputs...
|
||||
Enhancing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -277,99 +279,6 @@ export default function AIRecruiterForm({ onSubmit, isGenerating }: AIRecruiterF
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhancement Suggestions - Full Width */}
|
||||
{showSuggestions && (
|
||||
<div className="glass-panel rounded-lg p-4 border border-border bg-muted/30">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="font-sf font-medium text-sm flex items-center gap-2">
|
||||
<Lightbulb className="h-4 w-4 text-primary" />
|
||||
Enhancement Suggestions:
|
||||
</h3>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleToggleCollapse}
|
||||
className="h-6 w-6 p-0 hover:bg-slate-200"
|
||||
title={suggestionsCollapsed ? "Expand suggestions" : "Collapse suggestions"}
|
||||
>
|
||||
{suggestionsCollapsed ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!suggestionsCollapsed && (
|
||||
<>
|
||||
{enhancementError ? (
|
||||
<div className="text-sm text-red-600 bg-red-50 p-3 rounded-md">
|
||||
{enhancementError}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Left Column: Audience Brief Suggestions */}
|
||||
<div>
|
||||
{suggestions.audience_brief.length > 0 ? (
|
||||
<div>
|
||||
<h4 className="font-sf font-medium text-sm text-slate-800 mb-2 flex items-center gap-2">
|
||||
<Users className="h-4 w-4 text-blue-600" />
|
||||
Suggestions for your Audience Brief:
|
||||
</h4>
|
||||
<ul className="space-y-2 text-sm text-slate-700">
|
||||
{suggestions.audience_brief.map((suggestion, index) => (
|
||||
<li key={index} className="flex items-start gap-2">
|
||||
<span className="text-blue-600 mt-1.5 text-xs">•</span>
|
||||
<span className="flex-1">{suggestion}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No audience brief suggestions available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Column: Research Objective Suggestions */}
|
||||
<div>
|
||||
{suggestions.research_objective.length > 0 ? (
|
||||
<div>
|
||||
<h4 className="font-sf font-medium text-sm text-slate-800 mb-2 flex items-center gap-2">
|
||||
<FileText className="h-4 w-4 text-green-600" />
|
||||
Suggestions for your Research Objective:
|
||||
</h4>
|
||||
<ul className="space-y-2 text-sm text-slate-700">
|
||||
{suggestions.research_objective.map((suggestion, index) => (
|
||||
<li key={index} className="flex items-start gap-2">
|
||||
<span className="text-green-600 mt-1.5 text-xs">•</span>
|
||||
<span className="flex-1">{suggestion}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No research objective suggestions available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* No suggestions fallback - only show if both columns are empty */}
|
||||
{suggestions.audience_brief.length === 0 && suggestions.research_objective.length === 0 && (
|
||||
<div className="col-span-full text-sm text-muted-foreground text-center">
|
||||
No suggestions available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* AI Model and Persona Count - Half Width Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* LLM Model Selection */}
|
||||
|
|
@ -490,6 +399,42 @@ export default function AIRecruiterForm({ onSubmit, isGenerating }: AIRecruiterF
|
|||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* Assumptions Modal */}
|
||||
<Dialog open={showAssumptionsModal} onOpenChange={setShowAssumptionsModal}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-primary" />
|
||||
Brief Enhanced Successfully
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Your inputs have been enhanced with the following additions:
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
{assumptions.length > 0 ? (
|
||||
<ul className="space-y-2">
|
||||
{assumptions.map((assumption, index) => (
|
||||
<li key={index} className="flex items-start gap-2 text-sm">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>{assumption}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No specific assumptions were made during enhancement.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setShowAssumptionsModal(false)}>
|
||||
Got it
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue