Spaces:
Build error
Build error
| """ | |
| AI-Powered Startup Coach Personas for NAVADA | |
| Provides specialized coaching expertise across different startup domains | |
| """ | |
| import json | |
| import logging | |
| import random | |
| from typing import Dict, Any, List, Optional | |
| from datetime import datetime | |
| import openai | |
| from openai import AsyncOpenAI | |
| import os | |
| logger = logging.getLogger(__name__) | |
| class StartupCoachPersonas: | |
| """Manages AI-powered startup coaching personas with specialized expertise""" | |
| def __init__(self): | |
| self.client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| self.current_session = None | |
| self.persona_history = {} | |
| def get_available_personas(self) -> Dict[str, Dict[str, Any]]: | |
| """Get all available coaching personas with their specializations""" | |
| return { | |
| "sarah_strategic": { | |
| "name": "Sarah Chen - Strategic Advisor", | |
| "expertise": ["Business Strategy", "Market Entry", "Competitive Analysis", "Growth Planning"], | |
| "background": "Former McKinsey consultant, 3 successful exits as startup founder", | |
| "personality": "Analytical, direct, strategic thinker", | |
| "speaking_style": "Data-driven with real-world examples", | |
| "avatar": "π©βπΌ", | |
| "color": "#4F46E5" | |
| }, | |
| "marcus_technical": { | |
| "name": "Marcus Rodriguez - CTO Advisor", | |
| "expertise": ["Technical Architecture", "MVP Development", "Tech Stack Selection", "Scalability"], | |
| "background": "Ex-Google Principal Engineer, built platforms for 100M+ users", | |
| "personality": "Pragmatic, detail-oriented, solution-focused", | |
| "speaking_style": "Technical but accessible, focuses on implementation", | |
| "avatar": "π¨βπ»", | |
| "color": "#059669" | |
| }, | |
| "elena_financial": { | |
| "name": "Elena Thompson - Finance Expert", | |
| "expertise": ["Financial Modeling", "Fundraising", "Valuation", "Unit Economics"], | |
| "background": "Former VC Partner at Sequoia, CFO at 2 unicorns", | |
| "personality": "Meticulous, numbers-focused, investor mindset", | |
| "speaking_style": "Precise financial language with clear metrics", | |
| "avatar": "π©βπ°", | |
| "color": "#DC2626" | |
| }, | |
| "david_marketing": { | |
| "name": "David Kim - Growth Hacker", | |
| "expertise": ["Customer Acquisition", "Product Marketing", "Growth Strategies", "User Retention"], | |
| "background": "VP Growth at Airbnb, scaled 3 startups from 0 to 10M users", | |
| "personality": "Creative, experimental, customer-obsessed", | |
| "speaking_style": "Energetic with growth hacks and case studies", | |
| "avatar": "π¨βπ", | |
| "color": "#7C3AED" | |
| }, | |
| "lisa_legal": { | |
| "name": "Lisa Johnson - Legal & Compliance", | |
| "expertise": ["Corporate Structure", "IP Protection", "Regulatory Compliance", "Contracts"], | |
| "background": "Former startup lawyer at Wilson Sonsini, in-house counsel at Meta", | |
| "personality": "Thorough, risk-aware, protective", | |
| "speaking_style": "Clear legal guidance with practical implications", | |
| "avatar": "π©ββοΈ", | |
| "color": "#0891B2" | |
| }, | |
| "alex_operations": { | |
| "name": "Alex Chen - Operations Guru", | |
| "expertise": ["Operations Scaling", "Process Optimization", "Team Building", "Efficiency"], | |
| "background": "COO at 3 successful startups, operations consultant", | |
| "personality": "Systematic, efficiency-focused, team-oriented", | |
| "speaking_style": "Process-driven with actionable frameworks", | |
| "avatar": "π¨βπ§", | |
| "color": "#EA580C" | |
| } | |
| } | |
| async def initialize_coaching_session(self, persona_id: str, startup_context: Dict[str, Any]) -> Dict[str, Any]: | |
| """Initialize a coaching session with a specific persona""" | |
| try: | |
| personas = self.get_available_personas() | |
| if persona_id not in personas: | |
| return {"status": "error", "message": "Invalid persona ID"} | |
| persona = personas[persona_id] | |
| # Create coaching session context | |
| session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{persona_id}" | |
| self.current_session = { | |
| "session_id": session_id, | |
| "persona_id": persona_id, | |
| "persona": persona, | |
| "startup_context": startup_context, | |
| "conversation_history": [], | |
| "created_at": datetime.now().isoformat(), | |
| "focus_areas": [], | |
| "action_items": [] | |
| } | |
| # Initialize persona history if not exists | |
| if persona_id not in self.persona_history: | |
| self.persona_history[persona_id] = [] | |
| return { | |
| "status": "success", | |
| "session_id": session_id, | |
| "persona": persona, | |
| "welcome_message": await self._generate_welcome_message(persona, startup_context) | |
| } | |
| except Exception as e: | |
| logger.error(f"Error initializing coaching session: {e}") | |
| return {"status": "error", "message": str(e)} | |
| async def _generate_welcome_message(self, persona: Dict[str, Any], startup_context: Dict[str, Any]) -> str: | |
| """Generate personalized welcome message from the persona""" | |
| try: | |
| system_prompt = f""" | |
| You are {persona['name']}, a startup advisor with expertise in {', '.join(persona['expertise'])}. | |
| Background: {persona['background']} | |
| Personality: {persona['personality']} | |
| Speaking Style: {persona['speaking_style']} | |
| Generate a warm, personalized welcome message for this startup founder. Keep it under 150 words. | |
| Be specific about how you can help based on your expertise and their startup context. | |
| """ | |
| user_prompt = f""" | |
| Startup Context: | |
| - Industry: {startup_context.get('industry', 'Not specified')} | |
| - Stage: {startup_context.get('stage', 'Idea stage')} | |
| - Team Size: {startup_context.get('team_size', 'Solo founder')} | |
| - Current Challenge: {startup_context.get('current_challenge', 'General guidance needed')} | |
| - Brief Description: {startup_context.get('description', 'Early stage startup')} | |
| Generate a welcome message that shows you understand their situation and how you can specifically help. | |
| """ | |
| response = await self.client.chat.completions.create( | |
| model="gpt-4", | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt} | |
| ], | |
| max_tokens=200, | |
| temperature=0.7 | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| logger.error(f"Error generating welcome message: {e}") | |
| return f"Hi! I'm {persona['name']}. I'm excited to help you with your startup journey!" | |
| async def get_coaching_advice(self, query: str, context: Dict[str, Any] = None) -> Dict[str, Any]: | |
| """Get specialized coaching advice from the current persona""" | |
| try: | |
| if not self.current_session: | |
| return {"status": "error", "message": "No active coaching session"} | |
| persona = self.current_session["persona"] | |
| startup_context = self.current_session["startup_context"] | |
| # Build conversation context | |
| conversation_history = self.current_session["conversation_history"][-5:] # Last 5 exchanges | |
| system_prompt = f""" | |
| You are {persona['name']}, a startup advisor specializing in {', '.join(persona['expertise'])}. | |
| Background: {persona['background']} | |
| Personality: {persona['personality']} | |
| Speaking Style: {persona['speaking_style']} | |
| Provide specific, actionable advice based on your expertise. Always include: | |
| 1. Direct answer to their question | |
| 2. Specific next steps | |
| 3. Potential risks/considerations | |
| 4. Resources or tools to help | |
| Keep responses focused and under 300 words unless complex analysis is needed. | |
| """ | |
| # Prepare context for the AI | |
| context_text = f""" | |
| Startup Context: | |
| {json.dumps(startup_context, indent=2)} | |
| Previous Conversation: | |
| {json.dumps(conversation_history, indent=2) if conversation_history else "None"} | |
| Current Question: {query} | |
| """ | |
| if context: | |
| context_text += f"\nAdditional Context: {json.dumps(context, indent=2)}" | |
| response = await self.client.chat.completions.create( | |
| model="gpt-4", | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": context_text} | |
| ], | |
| max_tokens=500, | |
| temperature=0.7 | |
| ) | |
| advice = response.choices[0].message.content | |
| # Update conversation history | |
| self.current_session["conversation_history"].append({ | |
| "timestamp": datetime.now().isoformat(), | |
| "query": query, | |
| "advice": advice, | |
| "context": context | |
| }) | |
| # Extract action items if any | |
| action_items = await self._extract_action_items(advice) | |
| if action_items: | |
| self.current_session["action_items"].extend(action_items) | |
| return { | |
| "status": "success", | |
| "advice": advice, | |
| "persona": persona["name"], | |
| "expertise_areas": persona["expertise"], | |
| "action_items": action_items, | |
| "session_id": self.current_session["session_id"] | |
| } | |
| except Exception as e: | |
| logger.error(f"Error getting coaching advice: {e}") | |
| return {"status": "error", "message": str(e)} | |
| async def _extract_action_items(self, advice_text: str) -> List[str]: | |
| """Extract actionable items from coaching advice""" | |
| try: | |
| system_prompt = """ | |
| Extract specific, actionable items from this coaching advice. | |
| Return only concrete next steps the founder should take. | |
| Format as a JSON list of strings. | |
| If no clear action items, return empty list. | |
| """ | |
| response = await self.client.chat.completions.create( | |
| model="gpt-3.5-turbo", | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": advice_text} | |
| ], | |
| max_tokens=200, | |
| temperature=0.3 | |
| ) | |
| result = response.choices[0].message.content | |
| try: | |
| return json.loads(result) | |
| except json.JSONDecodeError: | |
| return [] | |
| except Exception as e: | |
| logger.error(f"Error extracting action items: {e}") | |
| return [] | |
| async def get_persona_handoff_recommendation(self, current_query: str) -> Dict[str, Any]: | |
| """Recommend which persona would be best suited for the current query""" | |
| try: | |
| personas = self.get_available_personas() | |
| system_prompt = """ | |
| Analyze the user's query and recommend the most suitable startup advisor persona. | |
| Consider the expertise areas and which advisor would provide the most valuable insights. | |
| Return your recommendation as JSON with: | |
| { | |
| "recommended_persona": "persona_id", | |
| "confidence": 0.8, | |
| "reasoning": "Why this persona is best suited", | |
| "alternative": "backup_persona_id" | |
| } | |
| """ | |
| personas_info = {pid: {"name": p["name"], "expertise": p["expertise"]} | |
| for pid, p in personas.items()} | |
| user_prompt = f""" | |
| Available Personas: | |
| {json.dumps(personas_info, indent=2)} | |
| User Query: {current_query} | |
| Current Session: {self.current_session["persona"]["name"] if self.current_session else "None"} | |
| """ | |
| response = await self.client.chat.completions.create( | |
| model="gpt-4", | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt} | |
| ], | |
| max_tokens=200, | |
| temperature=0.3 | |
| ) | |
| result = json.loads(response.choices[0].message.content) | |
| result["current_persona"] = self.current_session["persona_id"] if self.current_session else None | |
| return {"status": "success", **result} | |
| except Exception as e: | |
| logger.error(f"Error getting handoff recommendation: {e}") | |
| return {"status": "error", "message": str(e)} | |
| async def switch_persona(self, new_persona_id: str, handoff_context: str = None) -> Dict[str, Any]: | |
| """Switch to a different coaching persona with context handoff""" | |
| try: | |
| personas = self.get_available_personas() | |
| if new_persona_id not in personas: | |
| return {"status": "error", "message": "Invalid persona ID"} | |
| # Prepare handoff summary if switching from another persona | |
| handoff_summary = None | |
| if self.current_session: | |
| handoff_summary = await self._generate_handoff_summary() | |
| # Store current session in history | |
| if self.current_session: | |
| persona_id = self.current_session["persona_id"] | |
| self.persona_history[persona_id].append({ | |
| **self.current_session, | |
| "ended_at": datetime.now().isoformat() | |
| }) | |
| # Initialize new session | |
| startup_context = self.current_session["startup_context"] if self.current_session else {} | |
| if handoff_context: | |
| startup_context["handoff_context"] = handoff_context | |
| new_session = await self.initialize_coaching_session(new_persona_id, startup_context) | |
| if new_session["status"] == "success": | |
| # Add handoff summary to new session | |
| if handoff_summary: | |
| new_session["handoff_summary"] = handoff_summary | |
| return new_session | |
| except Exception as e: | |
| logger.error(f"Error switching persona: {e}") | |
| return {"status": "error", "message": str(e)} | |
| async def _generate_handoff_summary(self) -> str: | |
| """Generate a handoff summary for persona transitions""" | |
| try: | |
| if not self.current_session or not self.current_session["conversation_history"]: | |
| return "No previous conversation context." | |
| system_prompt = """ | |
| Create a concise handoff summary for transitioning between startup advisors. | |
| Include key discussion points, decisions made, and context the new advisor needs. | |
| Keep it under 150 words and focus on actionable insights. | |
| """ | |
| conversation_summary = { | |
| "persona": self.current_session["persona"]["name"], | |
| "expertise": self.current_session["persona"]["expertise"], | |
| "key_discussions": self.current_session["conversation_history"][-3:], | |
| "action_items": self.current_session["action_items"] | |
| } | |
| response = await self.client.chat.completions.create( | |
| model="gpt-3.5-turbo", | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": json.dumps(conversation_summary, indent=2)} | |
| ], | |
| max_tokens=200, | |
| temperature=0.5 | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| logger.error(f"Error generating handoff summary: {e}") | |
| return "Error generating handoff summary." | |
| def get_session_analytics(self) -> Dict[str, Any]: | |
| """Get analytics about coaching sessions""" | |
| try: | |
| total_sessions = sum(len(sessions) for sessions in self.persona_history.values()) | |
| persona_usage = { | |
| persona_id: len(sessions) | |
| for persona_id, sessions in self.persona_history.items() | |
| } | |
| most_used_persona = max(persona_usage.items(), key=lambda x: x[1]) if persona_usage else None | |
| return { | |
| "total_sessions": total_sessions, | |
| "active_session": bool(self.current_session), | |
| "current_persona": self.current_session["persona"]["name"] if self.current_session else None, | |
| "persona_usage": persona_usage, | |
| "most_used_persona": most_used_persona[0] if most_used_persona else None, | |
| "available_personas": len(self.get_available_personas()) | |
| } | |
| except Exception as e: | |
| logger.error(f"Error getting session analytics: {e}") | |
| return {"status": "error", "message": str(e)} | |
| class PersonaUIManager: | |
| """Manages UI components for persona selection and interaction""" | |
| def __init__(self): | |
| self.coach_personas = StartupCoachPersonas() | |
| def create_persona_selector(self) -> str: | |
| """Create HTML interface for persona selection""" | |
| personas = self.coach_personas.get_available_personas() | |
| persona_cards = "" | |
| for persona_id, persona in personas.items(): | |
| persona_cards += f""" | |
| <div class="persona-card" data-persona="{persona_id}" style="border-left: 4px solid {persona['color']}"> | |
| <div class="persona-header"> | |
| <span class="persona-avatar">{persona['avatar']}</span> | |
| <div class="persona-info"> | |
| <h3>{persona['name']}</h3> | |
| <p class="persona-background">{persona['background']}</p> | |
| </div> | |
| </div> | |
| <div class="persona-expertise"> | |
| <strong>Expertise:</strong> | |
| <div class="expertise-tags"> | |
| {' '.join([f'<span class="expertise-tag">{exp}</span>' for exp in persona['expertise']])} | |
| </div> | |
| </div> | |
| <div class="persona-style"> | |
| <strong>Style:</strong> {persona['speaking_style']} | |
| </div> | |
| <button class="select-persona-btn" onclick="selectPersona('{persona_id}')"> | |
| Choose {persona['name'].split(' ')[0]} | |
| </button> | |
| </div> | |
| """ | |
| return f""" | |
| <div id="persona-selector" class="persona-selector-container"> | |
| <div class="selector-header"> | |
| <h2>π― Choose Your Startup Coach</h2> | |
| <p>Select an AI-powered advisor specialized in your specific needs</p> | |
| </div> | |
| <div class="personas-grid"> | |
| {persona_cards} | |
| </div> | |
| <div class="coaching-context"> | |
| <h3>Tell us about your startup:</h3> | |
| <div class="context-form"> | |
| <input type="text" id="startup-name" placeholder="Startup name" /> | |
| <select id="startup-stage"> | |
| <option value="">Select stage</option> | |
| <option value="idea">Idea Stage</option> | |
| <option value="mvp">MVP Development</option> | |
| <option value="launch">Pre-Launch</option> | |
| <option value="growth">Growth Stage</option> | |
| <option value="scale">Scaling</option> | |
| </select> | |
| <input type="text" id="industry" placeholder="Industry/Sector" /> | |
| <textarea id="challenge" placeholder="Current biggest challenge or question..."></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| <style> | |
| .persona-selector-container {{ | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| border-radius: 20px; | |
| padding: 30px; | |
| margin: 20px 0; | |
| font-family: 'Inter', sans-serif; | |
| }} | |
| .selector-header {{ | |
| text-align: center; | |
| margin-bottom: 30px; | |
| }} | |
| .selector-header h2 {{ | |
| color: #2d3748; | |
| margin-bottom: 10px; | |
| }} | |
| .personas-grid {{ | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| }} | |
| .persona-card {{ | |
| background: white; | |
| border-radius: 15px; | |
| padding: 20px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |
| cursor: pointer; | |
| }} | |
| .persona-card:hover {{ | |
| transform: translateY(-5px); | |
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); | |
| }} | |
| .persona-header {{ | |
| display: flex; | |
| align-items: flex-start; | |
| margin-bottom: 15px; | |
| }} | |
| .persona-avatar {{ | |
| font-size: 2.5rem; | |
| margin-right: 15px; | |
| }} | |
| .persona-info h3 {{ | |
| margin: 0 0 5px 0; | |
| color: #2d3748; | |
| font-size: 1.1rem; | |
| }} | |
| .persona-background {{ | |
| color: #718096; | |
| font-size: 0.9rem; | |
| margin: 0; | |
| line-height: 1.4; | |
| }} | |
| .persona-expertise {{ | |
| margin-bottom: 15px; | |
| }} | |
| .expertise-tags {{ | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 5px; | |
| margin-top: 8px; | |
| }} | |
| .expertise-tag {{ | |
| background: #e2e8f0; | |
| color: #4a5568; | |
| padding: 4px 8px; | |
| border-radius: 15px; | |
| font-size: 0.8rem; | |
| font-weight: 500; | |
| }} | |
| .persona-style {{ | |
| color: #718096; | |
| font-size: 0.9rem; | |
| margin-bottom: 15px; | |
| }} | |
| .select-persona-btn {{ | |
| width: 100%; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| padding: 12px; | |
| border-radius: 25px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: opacity 0.3s ease; | |
| }} | |
| .select-persona-btn:hover {{ | |
| opacity: 0.9; | |
| }} | |
| .coaching-context {{ | |
| background: white; | |
| border-radius: 15px; | |
| padding: 25px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| }} | |
| .context-form {{ | |
| display: grid; | |
| gap: 15px; | |
| margin-top: 15px; | |
| }} | |
| .context-form input, | |
| .context-form select, | |
| .context-form textarea {{ | |
| padding: 12px 15px; | |
| border: 2px solid #e2e8f0; | |
| border-radius: 10px; | |
| font-size: 1rem; | |
| transition: border-color 0.3s ease; | |
| }} | |
| .context-form input:focus, | |
| .context-form select:focus, | |
| .context-form textarea:focus {{ | |
| outline: none; | |
| border-color: #667eea; | |
| }} | |
| .context-form textarea {{ | |
| min-height: 80px; | |
| resize: vertical; | |
| }} | |
| </style> | |
| <script> | |
| function selectPersona(personaId) {{ | |
| const startupContext = {{ | |
| name: document.getElementById('startup-name').value, | |
| stage: document.getElementById('startup-stage').value, | |
| industry: document.getElementById('industry').value, | |
| challenge: document.getElementById('challenge').value | |
| }}; | |
| // Send to backend | |
| if (window.chainlitAPI) {{ | |
| window.chainlitAPI.sendMessage({{ | |
| type: 'select_persona', | |
| persona_id: personaId, | |
| startup_context: startupContext | |
| }}); | |
| }} | |
| // Visual feedback | |
| document.querySelectorAll('.persona-card').forEach(card => {{ | |
| card.style.opacity = '0.5'; | |
| }}); | |
| document.querySelector(`[data-persona="${{personaId}}"]`).style.opacity = '1'; | |
| document.querySelector(`[data-persona="${{personaId}}"]`).style.background = '#f0fff4'; | |
| }} | |
| </script> | |
| """ |