milestoneme / app.py
luxananda's picture
Upload 3 files
5a48db7 verified
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import streamlit as st
from pdf_processor import PDFProcessor
import openai
from dotenv import load_dotenv
from langdetect import detect, LangDetectException
from deep_translator import GoogleTranslator
import random
from fpdf import FPDF
from datetime import datetime
import io
import unicodedata
import re
# Load environment variables
load_dotenv()
# Supported languages
SUPPORTED_LANGUAGES = {
'en': 'English',
'es': 'Spanish',
'fr': 'French',
'de': 'German',
'it': 'Italian',
'pt': 'Portuguese',
'ru': 'Russian',
'zh-CN': 'Chinese (Simplified)',
'ja': 'Japanese',
'ko': 'Korean',
'hi': 'Hindi',
'ta': 'Tamil'
}
CATEGORY_LIST = [
'All',
'CUSTOMER OBSESSION',
'OWNERSHIP',
'INVENT AND SIMPLIFY',
'ARE RIGHT, A LOT',
'HIRE AND DEVELOP THE BEST',
'INSIST ON THE HIGHEST STANDARDS',
'THINK BIG',
'BIAS FOR ACTION',
'BEING FRUGAL (FRUGALITY)',
'EARN TRUST',
'DIVE DEEP',
'DELIVER RESULTS',
'HAVE BACKBONE: DISAGREE AND COMMIT',
'LEARN & BE CURIOUS',
'SUCCESS & SCALE BRING BROAD RESPONSIBILITY',
"STRIVE TO BE EARTH'S BEST"
]
PDF_PATH = 'Milestone_Useme.pdf'
PROMPT_PATH = 'prompt.txt'
st.set_page_config(page_title="MilestoneMe - Self-Awareness Practice Partner", layout="centered")
@st.cache_resource
# Summarize each assistant feedback to a single key point (first sentence or summary)
def summarize_feedback(feedbacks):
summary_points = []
for fb in feedbacks:
# Remove greetings and filler (e.g., lines starting with 'Hey', 'Hi', etc.)
lines = [line for line in fb.split('\n') if not re.match(r'^(hey|hi|hello|how|wow|great|it\'s|overall|i love|i\'d|i am|i\'m|thank)', line.strip().lower())]
# Extract lines that look like pointers or suggestions
pointers = [line.strip('-•* ').strip() for line in lines if line.strip() and (line.strip().startswith('-') or line.strip().startswith('•') or 'suggest' in line.lower() or 'could' in line.lower() or 'consider' in line.lower() or 'improve' in line.lower() or 'development' in line.lower() or 'next step' in line.lower() or 'area' in line.lower())]
# If no pointers found, take short lines as possible pointers
if not pointers:
pointers = [line.strip() for line in lines if 10 < len(line.strip()) < 120]
summary_points.extend(pointers)
return summary_points
def get_pdf_processor():
processor = PDFProcessor()
processor.process_pdf(PDF_PATH)
return processor
pdf_processor = get_pdf_processor()
with open(PROMPT_PATH, 'r') as f:
SYSTEM_PROMPT = f.read()
def is_language_supported(lang_code):
return lang_code in SUPPORTED_LANGUAGES
def detect_language(text):
try:
lang = detect(text)
if lang in ['zh-cn', 'zh']:
return 'zh-CN'
if lang in SUPPORTED_LANGUAGES:
return lang
return 'en'
except LangDetectException:
return 'en'
def translate_text(text, target_lang, source_lang='en'):
try:
if not text or target_lang == source_lang:
return text
translator = GoogleTranslator(source=source_lang, target=target_lang)
return translator.translate(text)
except Exception:
return text
# 1. Welcome messages in all provided languages
WELCOME_MESSAGES = {
'en': 'Welcome! Ready to practice self-awareness?',
'es': '¡Bienvenido! ¿Listo para practicar la autoconciencia?',
'fr': 'Bienvenue ! Prêt à pratiquer la conscience de soi ?',
'de': 'Willkommen! Bereit, Selbstbewusstsein zu üben?',
'it': "Benvenuto! Pronto a praticare l'autoconsapevolezza?",
'pt': 'Bem-vindo! Pronto para praticar a autoconsciência?',
'ru': 'Добро пожаловать! Готовы практиковать самосознание?',
'zh-CN': '欢迎!准备好练习自我觉察了吗?',
'ja': 'ようこそ!自己認識の練習を始めましょうか?',
'ko': '환영합니다! 자기 인식을 연습할 준비가 되셨나요?',
'hi': 'स्वागत है! आत्म-जागरूकता का अभ्यास करने के लिए तैयार हैं?',
'ta': 'வரவேற்கிறேன்! சுய விழிப்புணர்வை பயிற்சி செய்ய தயாரா?'
}
st.title("MilestoneMe - Self-Awareness Practice Partner")
with st.sidebar:
st.header("Session Setup")
api_key = st.text_input("OpenAI API Key", type="password")
nickname = st.text_input("Nickname")
category = st.selectbox("Category", CATEGORY_LIST)
if category == "All":
content_sections = pdf_processor.conn.execute(
"""
SELECT DISTINCT section FROM pdf_content WHERE length(section) > 50 ORDER BY random() LIMIT 5
"""
).fetchall()
content_sections = [row[0] for row in content_sections]
else:
# Fetch all questions for the selected category using case-insensitive match on section column
content_sections = pdf_processor.conn.execute(
"""
SELECT DISTINCT section FROM pdf_content WHERE length(section) > 50 AND lower(section) LIKE ?
ORDER BY random()
""", [f'{category.lower()}%']
).fetchall()
content_sections = [row[0] for row in content_sections]
language = st.selectbox("Language", list(SUPPORTED_LANGUAGES.keys()), format_func=lambda x: SUPPORTED_LANGUAGES[x])
# Show welcome message in selected language
st.info(WELCOME_MESSAGES.get(language, WELCOME_MESSAGES['en']))
start = st.button("Start Session")
if 'session_started' not in st.session_state:
st.session_state.session_started = False
if 'history' not in st.session_state:
st.session_state.history = []
if 'category' not in st.session_state:
st.session_state.category = 'All'
if 'num_questions' not in st.session_state:
st.session_state.num_questions = 5
if 'language' not in st.session_state:
st.session_state.language = 'en'
if start and api_key and nickname:
st.session_state.session_started = True
st.session_state.history = []
st.session_state.category = category
if category == "All":
st.session_state.num_questions = 5
st.session_state.language = language
# 2. Start by saying 'hi' in the selected language
hi_message = {
'en': 'Hi!',
'es': '¡Hola!',
'fr': 'Salut!',
'de': 'Hallo!',
'it': 'Ciao!',
'pt': 'Oi!',
'ru': 'Привет!',
'zh-CN': '嗨!',
'ja': 'こんにちは!',
'ko': '안녕하세요!',
'hi': 'नमस्ते!',
'ta': 'வணக்கம்!'
}.get(language, 'Hi!')
st.session_state.history.append((hi_message, 'assistant'))
st.success(f"Session started for {nickname}!")
# Helper: Dummy article suggestions (replace with real logic or API as needed)
ARTICLE_SUGGESTIONS = {
'Customer Obsession': [
('How to Build Customer Obsession', 'https://hbr.org/2020/05/what-it-takes-to-build-customer-obsession'),
('Customer-Centric Mindset', 'https://www.mckinsey.com/business-functions/marketing-and-sales/our-insights/the-three-cs-of-customer-satisfaction-consistency-consistency-consistency')
],
'Ownership': [
('Extreme Ownership: Leadership Principles', 'https://www.leadershipnow.com/ownership.html')
],
# ... add more as needed ...
}
if st.session_state.session_started:
st.subheader(f"Chat with your Self-Awareness Practice Partner ({SUPPORTED_LANGUAGES[st.session_state.language]})")
# 3. Add user notes
st.markdown(
"<div style='color:#888; font-size:14px; margin-bottom:10px;'>\n"
"<b>Note:</b> Sometimes you may need to click the <b>Send</b> button more than once.<br>"
"Pressing <b>Enter</b> will not send the message; you must click <b>Send</b>."
"</div>", unsafe_allow_html=True
)
for msg, role in st.session_state.history:
if role == 'user':
st.markdown(f"**You:** {msg}")
else:
# Extract category from the first line of the message if present
lines = msg.split('\n')
category_line = lines[0].strip() if lines else ''
question_body = '\n'.join(lines[1:]).strip() if len(lines) > 1 else msg
st.markdown(f"<div style='background:#e3f0ff; color:#174ea6; padding:10px; border-radius:8px; margin-bottom:8px;'><b>Category:</b> {category_line}<br><b>Partner:</b> {question_body}</div>", unsafe_allow_html=True)
# Safely initialize session state before widget
if 'user_input' not in st.session_state:
st.session_state['user_input'] = ''
if 'clear_input_flag' not in st.session_state:
st.session_state['clear_input_flag'] = False
# Clear input if flag is set (safe for Streamlit/Hugging Face)
if st.session_state['clear_input_flag']:
st.session_state['user_input'] = ''
st.session_state['clear_input_flag'] = False
# Create the input widget
user_input = st.text_area("Your message", key="user_input", height=80, help="Shift+Enter for new line")
send = st.button("Send")
next_question = st.button("Next Question")
# Determine if next question should be triggered
next_question_triggered = False
if send and user_input.strip().lower() in ["hi", "hello", "start", "new question", "next question"]:
next_question_triggered = True
elif next_question:
next_question_triggered = True
if send or next_question:
if next_question_triggered:
message_to_send = "hi"
else:
message_to_send = user_input.strip()
detected_lang = detect_language(message_to_send)
translated_message = message_to_send
if detected_lang != 'en':
translated_message = translate_text(message_to_send, 'en', detected_lang)
client = openai.OpenAI(api_key=api_key)
st.session_state.history.append((message_to_send, 'user'))
# If greeting or new question
if translated_message.lower() in ['hi', 'hello', 'start', 'new question', 'next question']:
if category == "All":
content_sections = pdf_processor.conn.execute(
"""
SELECT DISTINCT section FROM pdf_content WHERE length(section) > 50 ORDER BY random() LIMIT 5
"""
).fetchall()
content_sections = [row[0] for row in content_sections]
else:
content_sections = pdf_processor.conn.execute(
"""
SELECT DISTINCT section FROM pdf_content WHERE length(section) > 50 AND lower(section) LIKE ?
ORDER BY random()
""", [f'{category.lower()}%']
).fetchall()
content_sections = [row[0] for row in content_sections]
context = "\n".join(content_sections)
# Extract the real category from the first line of the first section
real_category = content_sections[0].split('\n')[0].strip() if content_sections else category
question_prompt = f"""You are talking to {nickname}. Based on this content from our interview materials:\n\n{context}\n\nGenerate a conversational behavioral self-awareness question. Make it feel natural and friendly, like a casual conversation. Greet {nickname} by name in a warm way before asking the question.\nThe question should follow the STAR method principles but be asked in a warm, engaging way.\nStart with a brief friendly opener before asking the question.\n\nDO NOT mention STAR method or behavioral interview in your response. Just ask the question naturally."""
question_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question_prompt}
],
temperature=0.7,
max_tokens=500
)
response_text = question_response.choices[0].message.content
if st.session_state.language != 'en':
response_text = translate_text(response_text, st.session_state.language)
# Prepend the real category to the question
response_text = f"{real_category}\n{response_text}"
st.session_state.history.append((response_text, 'assistant'))
st.session_state['clear_input_flag'] = True
try:
st.rerun()
except AttributeError:
pass # For older Streamlit versions, do nothing
else:
relevant_content = pdf_processor.get_relevant_content(translated_message)
context = "\n".join(relevant_content) if relevant_content else ""
feedback_prompt = f"""As a Self-Awareness Practice Partner, you are helping {nickname} reflect and grow. Based on this context:\n\n{context}\n\n{nickname} has provided this response:\n{translated_message}\n\nGive friendly, constructive feedback following these guidelines:\n1. Start with positive reinforcement and address {nickname} by name\n2. If the STAR elements are missing, gently ask for more details about specific parts\n3. Ask a relevant follow-up question to dig deeper\n4. Specifically check if {nickname} has articulated the dollar impact and cost saved for the customer. If not, suggest how to include these.\n5. Suggest ways to better articulate their success and ensure they are representing their achievements in the best possible way.\n6. Keep the tone warm, supportive, and conversational—like a peer, not a formal interviewer.\n\nRemember: Your goal is to help {nickname} build self-awareness and communicate their impact clearly."""
feedback_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": feedback_prompt}
],
temperature=0.7,
max_tokens=500
)
response_text = feedback_response.choices[0].message.content
if st.session_state.language != 'en':
response_text = translate_text(response_text, st.session_state.language)
st.session_state.history.append((response_text, 'assistant'))
st.session_state['clear_input_flag'] = True
try:
st.rerun()
except AttributeError:
pass # For older Streamlit versions, do nothing
# 1. End Session / View Results button
if st.button('End Session / View Results'):
st.session_state['show_results'] = True
# 2. Show modal with complete feedback if requested
if st.session_state.get('show_results', False):
with st.expander('Your 360° Feedback & Results', expanded=True):
st.header('Complete Feedback')
# Collect all assistant feedback
feedbacks = [msg for msg, role in st.session_state.history if role == 'assistant']
summary_points = summarize_feedback(feedbacks)
strengths = [pt for pt in summary_points if 'good' in pt.lower() or 'great' in pt.lower() or 'well' in pt.lower()]
improvements = [pt for pt in summary_points if 'improve' in pt.lower() or 'could' in pt.lower() or 'suggest' in pt.lower() or 'missing' in pt.lower()]
next_steps = [pt for pt in summary_points if 'next' in pt.lower() or 'try' in pt.lower() or 'consider' in pt.lower()]
consolidated_feedback = ''
if strengths:
consolidated_feedback += '**Strengths:**\n' + '\n'.join(f'- {pt}' for pt in strengths) + '\n'
if improvements:
consolidated_feedback += '**Areas for Improvement:**\n' + '\n'.join(f'- {pt}' for pt in improvements) + '\n'
if next_steps:
consolidated_feedback += '**Next Steps:**\n' + '\n'.join(f'- {pt}' for pt in next_steps) + '\n'
if not consolidated_feedback:
consolidated_feedback = 'No feedback available.'
for i, fb in enumerate(feedbacks, 1):
st.markdown(f"**Feedback {i}:** {fb}")
# 3. 360 Feedback Chart (dummy example)
st.subheader('360° Feedback Overview')
# Dummy scores for demonstration
categories = ['Customer Obsession', 'Ownership', 'Invent & Simplify', 'Earn Trust']
scores = [random.randint(60, 100) for _ in categories]
st.bar_chart({"Category": categories, "Score": scores})
# 4. Suggest articles for areas of excellence/improvement
st.subheader('Suggested Articles for Growth')
for cat, score in zip(categories, scores):
if score < 80 and cat in ARTICLE_SUGGESTIONS:
st.markdown(f"**{cat}:**")
for title, url in ARTICLE_SUGGESTIONS[cat]:
st.markdown(f"- [{title}]({url})")
# Before the PDF export block, define recommendations and article_links as empty dicts if not already defined
recommendations = {}
article_links = {}
# Prepare PDF content
# if st.button('Download as PDF'):
# pdf = FPDF()
# pdf.add_page()
# pdf.set_font('Arial', 'B', 18)
# pdf.cell(0, 10, sanitize_latin1('MilestoneMe 360° Feedback Report'), ln=True, align='C')
# pdf.set_font('Arial', '', 12)
# pdf.cell(0, 10, sanitize_latin1(f'Name: {nickname}'), ln=True)
# pdf.cell(0, 10, sanitize_latin1(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M')}",), ln=True)
# pdf.ln(5)
# pdf.set_font('Arial', 'B', 14)
# pdf.cell(0, 10, 'Consolidated Feedback', ln=True)
# pdf.set_font('Arial', '', 12)
# pdf.multi_cell(0, 8, sanitize_latin1(consolidated_feedback))
# pdf.ln(3)
# pdf.set_font('Arial', 'B', 14)
# pdf.cell(0, 10, '360° Feedback Table', ln=True)
# pdf.set_font('Arial', 'B', 12)
# pdf.cell(60, 8, sanitize_latin1('Category'), 1)
# pdf.cell(30, 8, sanitize_latin1('Score'), 1, ln=True)
# pdf.set_font('Arial', '', 12)
# for cat, score in zip(categories, scores):
# pdf.cell(60, 8, sanitize_latin1(cat), 1)
# pdf.cell(30, 8, sanitize_latin1(str(score)), 1, ln=True)
# pdf.ln(3)
# pdf.set_font('Arial', 'B', 14)
# pdf.cell(0, 10, 'Recommendations', ln=True)
# pdf.set_font('Arial', '', 12)
# for cat, rec in recommendations.items():
# pdf.cell(0, 8, sanitize_latin1(f'{cat}: {rec}'), ln=True)
# pdf.ln(3)
# pdf.set_font('Arial', 'B', 14)
# pdf.cell(0, 10, 'Further Reading', ln=True)
# pdf.set_font('Arial', '', 12)
# for cat, links in article_links.items():
# pdf.cell(0, 8, sanitize_latin1(f'{cat}:'), ln=True)
# for title, url in links:
# pdf.cell(0, 8, sanitize_latin1(f' {title}: {url}'), ln=True)
# pdf_output = io.BytesIO()
# pdf.output(pdf_output)
# st.download_button('Download PDF', data=pdf_output.getvalue(), file_name='milestoneme_feedback.pdf', mime='application/pdf')
st.button('Close', on_click=lambda: st.session_state.update({'show_results': False}))
# Optionally, add a manual clear button for user
if st.button("Clear Input"):
st.session_state['user_input'] = ''
st.markdown("""
---
<div style='text-align:center; color:#555; font-size:16px;'>
<hr style='margin:30px 0;'>
<p>Let's connect and grow together! 🚀<br>
For feedback, collaboration, or just to say hi, reach out on:<br>
<a href='https://www.linkedin.com/in/lakshmivenkatesh/' target='_blank' style='color:#1976d2; font-weight:bold; text-decoration:none;'>LinkedIn</a>
&nbsp;|&nbsp;
<a href='https://medium.com/@luxananda' target='_blank' style='color:#00ab6c; font-weight:bold; text-decoration:none;'>Medium</a>
<br><span style='font-size:13px; color:#888;'>(I love meeting fellow learners and builders!)</span></p>
</div>
""", unsafe_allow_html=True)
# Helper function to sanitize text for latin-1 encoding
def sanitize_latin1(text):
return unicodedata.normalize('NFKD', text).encode('latin-1', 'replace').decode('latin-1')