| import json, os |
| from io import StringIO |
| import pandas as pd |
| import streamlit as st |
| from snowflake.snowpark import Session |
| from bs4 import BeautifulSoup |
| from Messaging_system.Permes import Permes |
| from dotenv import load_dotenv |
| load_dotenv() |
|
|
|
|
| |
| |
| |
| @st.cache_data |
| def load_data(buf) -> pd.DataFrame: |
| return pd.read_csv(buf) |
|
|
| def load_config_(file_path: str) -> dict: |
| with open(file_path) as f: |
| return json.load(f) |
|
|
| def get_credential(key): |
| return os.getenv(key) or st.secrets.get(key) |
|
|
|
|
|
|
| |
| AUTHORIZED_EMAILS = { |
| "danial@musora.com", |
| "danial.ebrat@gmail.com", |
| "simon@musora.com", |
| "una@musora.com", |
| "mark@musora.com", |
| "gabriel@musora.com", |
| "nikki@musora.com" |
| } |
|
|
|
|
| |
| VALID_TOKEN = get_credential("APP_TOKEN") |
|
|
| |
| def verify_login(email: str, token: str) -> bool: |
| """True β’ both the address and the token are valid.""" |
| return (email.lower().strip() in AUTHORIZED_EMAILS) and (token == VALID_TOKEN) |
|
|
| |
| if not st.session_state.get("authenticated", False): |
|
|
| st.title("π Sign-in") |
| st.markdown( |
| "Access is limited to authorised users. " |
| "Enter your **e-mail** and the shared **access token**." |
| ) |
|
|
| with st.form("login"): |
| email = st.text_input("e-mail") |
| token = st.text_input("access token", type="password") |
| submitted = st.form_submit_button("Log in") |
|
|
| if submitted: |
| if verify_login(email, token): |
| st.session_state.authenticated = True |
| st.session_state.user_email = email |
| st.success("Login successful β redirectingβ¦") |
| st.rerun() |
| else: |
| st.error("β Invalid e-mail or token") |
|
|
| |
| st.stop() |
|
|
|
|
|
|
|
|
| def init_state() -> None: |
| defaults = dict( |
| involve_recsys_result=False, |
| involve_last_interaction=False, |
| valid_instructions="", |
| invalid_instructions="", |
| messaging_type="push", |
| generated=False, |
| include_recommendation=False, |
| data=None, brand=None, recsys_contents=[], csv_output=None, |
| users_message=None, messaging_mode=None, target_column=None, |
| ugc_column=None, identifier_column=None, input_validator=None, |
| selected_input_features=None, selected_features=None, |
| additional_instructions=None, segment_info="", message_style="", |
| sample_example="", CTA="", all_features=None, number_of_messages=1, |
| instructionset={}, segment_name="", number_of_samples=10, |
| selected_source_features=[], platform=None, generate_clicked=False, |
| ) |
| for k, v in defaults.items(): |
| st.session_state.setdefault(k, v) |
|
|
| |
| |
| |
| st.set_page_config( |
| page_title="Personalized Message Generator", |
| page_icon="π¬", |
| layout="wide", |
| initial_sidebar_state="expanded" |
| ) |
|
|
| st.markdown( |
| """ |
| <style> |
| html, body, [class*="css"] { |
| background-color:#0d0d0d; |
| color:#ffd700; |
| } |
| .stButton>button, .stDownloadButton>button { |
| border-radius:8px; |
| background:#ffd700; |
| color:#0d0d0d; |
| font-weight:600; |
| } |
| .stTabs [data-baseweb="tab"] { |
| font-weight:600; |
| } |
| .stTabs [aria-selected="true"] { |
| color:#ffd700; |
| } |
| h1, h2, h3 {color:#ffd700;} |
| .small {font-size:0.85rem; opacity:0.7;} |
| </style> |
| """, |
| unsafe_allow_html=True |
| ) |
|
|
| |
| |
| |
| init_state() |
| with st.sidebar: |
| |
| |
| uploaded_file = "Data/Singeo_Camp.csv" |
| if uploaded_file: |
| st.session_state.data = load_data(uploaded_file) |
| st.success("File loaded!") |
|
|
| st.markdown("---") |
|
|
| if st.session_state.data is not None: |
| |
| |
| |
| |
| |
| |
| id_col = "user_id" |
| st.session_state.identifier_column = id_col |
|
|
|
|
| |
| st.selectbox( |
| "Brand *", |
| ["drumeo", "pianote", "guitareo", "singeo"], |
| key="brand", |
| ) |
|
|
| |
| st.text_area("Segment info *", key="segment_info") |
| st.text_area("CTA (Call to Action) *", key="CTA") |
| with st.expander("π§ Optional tone & examples"): |
| st.text_area("Message style", key="message_style", |
| placeholder="Be kind and friendlyβ¦") |
| st.text_area("Additional instructions", key="additional_instructions", |
| placeholder="e.g. Mention the number weeks since their last practice") |
| st.text_area("Sample example", key="sample_example", |
| placeholder="Hello! We have craftedβ¦") |
| st.number_input("Number of samples (default = 10)", 1, 50, |
| key="number_of_samples") |
|
|
| |
| st.number_input("Sequential messages / user", 1, 12, 1, |
| key="number_of_messages") |
| st.text_input("Segment name", key="segment_name", |
| placeholder="no_recent_activity") |
| if st.session_state.number_of_messages > 1: |
| st.caption("Additional per-message instructions") |
| for i in range(1, st.session_state.number_of_messages + 1): |
| st.text_input(f"Message {i} instruction", |
| key=f"instr_{i}") |
|
|
| |
| st.multiselect( |
| "Source features", |
| ["instrument", "weeks_since_last_interaction", |
| "birthday_reminder"], |
| default=["instrument"], |
| key="selected_source_features" |
| ) |
|
|
| |
| st.checkbox("Include content recommendation", key="include_recommendation") |
| if st.session_state.include_recommendation: |
| st.multiselect( |
| "Recommendation types", |
| ["song", "workout", "quick_tips", "course"], |
| key="recsys_contents" |
| ) |
|
|
| st.markdown("---") |
|
|
| if st.button("π Generate messages", key="generate"): |
| st.session_state.generate_clicked = True |
| st.session_state.generated = False |
|
|
| |
| |
|
|
|
|
| |
| |
| |
| tab0, tab2 = st.tabs( |
| ["π Data preview", "π¨ Results"]) |
|
|
| |
| with tab0: |
| st.header("π Data preview") |
| if st.session_state.data is not None: |
| st.dataframe(st.session_state.data.head(100)) |
| else: |
| st.info("Upload a CSV to preview it here.") |
|
|
|
|
| |
| with tab2: |
| st.header("π¨ Generated messages") |
| |
| if st.session_state.generate_clicked and not st.session_state.generated: |
|
|
| |
| if not st.session_state.CTA.strip() or not st.session_state.segment_info.strip() or not st.session_state.brand.strip(): |
| st.error("CTA, Segment info, and brand are mandatory π«") |
| st.stop() |
|
|
| if st.session_state.get("generate_clicked", False): |
|
|
| |
| conn = dict( |
| user=get_credential("snowflake_user"), |
| password=get_credential("snowflake_password"), |
| account=get_credential("snowflake_account"), |
| role=get_credential("snowflake_role"), |
| database=get_credential("snowflake_database"), |
| warehouse=get_credential("snowflake_warehouse"), |
| schema=get_credential("snowflake_schema") |
| ) |
| config = load_config_("Config_files/message_system_config.json") |
| session = Session.builder.configs(conn).create() |
|
|
| |
| st.session_state.messaging_mode = ( |
| "recsys_result" if st.session_state.include_recommendation |
| else "message" |
| ) |
| st.session_state.involve_recsys_result = st.session_state.include_recommendation |
| st.session_state.instructionset = { |
| i: st.session_state.get(f"instr_{i}") |
| for i in range(1, st.session_state.number_of_messages + 1) |
| if st.session_state.get(f"instr_{i}", "").strip() |
| } |
|
|
| |
| prog = st.progress(0) |
| status = st.empty() |
|
|
| def cb(done, total): |
| pct = int(done / total * 100) |
| prog.progress(pct) |
| status.write(f"{pct}%") |
|
|
| permes = Permes() |
| df_msg = permes.create_personalize_messages( |
| session=session, |
| users=st.session_state.data, |
| brand=st.session_state.brand, |
| config_file=config, |
| openai_api_key=get_credential("OPENAI_API"), |
| CTA=st.session_state.CTA, |
| segment_info=st.session_state.segment_info, |
| number_of_samples=st.session_state.number_of_samples, |
| message_style=st.session_state.message_style, |
| sample_example=st.session_state.sample_example, |
| selected_input_features=st.session_state.selected_features, |
| selected_source_features=st.session_state.selected_source_features, |
| additional_instructions=st.session_state.additional_instructions, |
| platform=st.session_state.messaging_type, |
| involve_recsys_result=st.session_state.involve_recsys_result, |
| messaging_mode=st.session_state.messaging_mode, |
| identifier_column=st.session_state.identifier_column, |
| target_column=st.session_state.target_column, |
| recsys_contents=st.session_state.recsys_contents, |
| progress_callback=cb, |
| number_of_messages=st.session_state.number_of_messages, |
| instructionset=st.session_state.instructionset, |
| segment_name=st.session_state.segment_name |
| ) |
|
|
| |
| st.session_state.users_message = df_msg |
| st.session_state.csv_output = df_msg.to_csv( |
| index=False, encoding="utf-8-sig") |
| st.session_state.generated = True |
| st.session_state.generate_clicked = False |
| prog.empty(); status.empty() |
| st.balloons() |
|
|
| |
| if st.session_state.get("generated", False): |
| df = st.session_state.users_message |
| id_col = st.session_state.identifier_column or "" |
| id_col_lower = id_col.lower() |
|
|
| |
| for i, (_, row) in enumerate(df.iterrows(), start=1): |
| user_id = row.get(id_col_lower, "(no ID)") |
| with st.expander(f"{i}. User ID: {user_id}", expanded=(i == 1)): |
| |
| st.write("##### π€ Features") |
| feats = st.session_state.selected_source_features or [] |
| cols = st.columns(3) |
| for idx, feature in enumerate(feats): |
| val = row.get(feature, "β") |
| cols[idx % 3].markdown(f"**{feature}**: {val}") |
|
|
| st.markdown("---") |
|
|
| |
| st.write("##### βοΈ Messages") |
| raw = row.get("message", "") |
| |
| if isinstance(raw, str): |
| try: |
| blob = json.loads(raw) |
| except json.JSONDecodeError: |
| st.error(f"Could not parse JSON for user {user_id}") |
| continue |
| elif isinstance(raw, dict) or isinstance(raw, list): |
| blob = raw |
| else: |
| blob = {} |
|
|
| |
| if isinstance(blob, dict): |
| seq = blob.get("messages_sequence", []) |
| elif isinstance(blob, list): |
| seq = blob |
| else: |
| seq = [] |
|
|
| |
| if not isinstance(seq, list): |
| seq = [seq] |
|
|
| |
| for j, msg in enumerate(seq, start=1): |
| if not isinstance(msg, dict): |
| |
| st.markdown(f"**{j}. (no header)**") |
| st.markdown(str(msg)) |
| st.markdown("---") |
| continue |
|
|
| header = msg.get("header", "(no header)") |
| st.markdown(f"**{j}. {header}**") |
|
|
| |
| title = msg.get("title") |
| if title: |
| st.markdown(f"**Title:** {title}") |
|
|
| |
| thumb = msg.get("thumbnail_url") or row.get("thumbnail_url") |
| if thumb: |
| st.image(thumb, width=150) |
|
|
| |
| body = msg.get("message", "") |
| st.markdown(body) |
|
|
| |
| url = msg.get("web_url_path") |
| if url: |
| st.markdown(f"[Read more]({url})") |
|
|
| st.markdown("---") |
|
|
|
|