Spaces:
Sleeping
Sleeping
File size: 7,231 Bytes
c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d c754eca ad3c52d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | # app.py
import streamlit as st
import os
from typing import List, Dict
import json
from config import MODEL_CHOICES, DEFAULT_MODEL, DEFAULT_PERSONA_PATH
from utils import (
get_personas,
format_response_line,
detect_insight_or_concern,
extract_persona_response,
build_sentiment_summary,
build_heatmap_chart,
save_personas,
)
from ai_helpers import generate_response_with_retry, generate_feedback_report
# -------------------------
# Page config & state
# -------------------------
st.set_page_config(page_title="Persona Feedback Simulator", page_icon="π¬", layout="wide")
if "conversation_history" not in st.session_state:
st.session_state.conversation_history = ""
if "api_key" not in st.session_state:
st.session_state.api_key = ""
# -------------------------
# Sidebar: API key, model, personas upload
# -------------------------
st.sidebar.header("π API Configuration")
api_env = os.getenv("OPENAI_API_KEY", "")
api_key_input = st.sidebar.text_input("OpenAI API Key (or set OPENAI_API_KEY variable)", type="password", value=st.session_state.api_key or api_env)
if api_key_input:
st.session_state.api_key = api_key_input
import openai
openai.api_key = api_key_input
else:
st.sidebar.info("Enter OpenAI API key to enable generation.")
model_choice = st.sidebar.selectbox("Model", MODEL_CHOICES, index=MODEL_CHOICES.index(DEFAULT_MODEL))
st.sidebar.markdown("---")
st.sidebar.header("π₯ Personas")
uploaded = st.sidebar.file_uploader("Upload personas.json (optional)", type=["json"])
personas = get_personas(uploaded, path=DEFAULT_PERSONA_PATH)
st.sidebar.metric("Total Personas", len(personas))
# -------------------------
# Main UI - Feature input
# -------------------------
st.title("π¬ Persona Feedback Simulator")
st.header("π Feature Description")
tabs = st.tabs(["Text", "Files"])
with tabs[0]:
text_desc = st.text_area("Describe your feature", height=160)
with tabs[1]:
uploaded_files = st.file_uploader("Upload wireframes / mockups", accept_multiple_files=True, type=["png","jpg","jpeg","pdf"])
feature_inputs = {
"Text": text_desc or "",
"Files": [f.name for f in (uploaded_files or [])]
}
st.markdown("---")
# -------------------------
# Persona selection
# -------------------------
st.header("π₯ Choose Personas")
if not personas:
st.warning("No personas available. Create personas in the sidebar or upload a personas.json.")
selected_personas: List[Dict] = []
else:
labels = [f"{p['name']} ({p.get('occupation','')})" for p in personas]
defaults = labels[:3]
selected_labels = st.multiselect("Select personas:", labels, default=defaults)
selected_personas = [p for p in personas if f"{p['name']} ({p.get('occupation','')})" in selected_labels]
# -------------------------
# Ask / Report / Clear controls
# -------------------------
st.header("π Ask Your Question")
question = st.text_input("Question to personas")
c1, c2, c3 = st.columns([2, 2, 1])
ask_btn = c1.button("π― Ask")
report_btn = c2.button("π Generate Report")
clear_btn = c3.button("ποΈ Clear")
if ask_btn:
if not st.session_state.api_key:
st.warning("Please set your OpenAI API key in the sidebar or via OPENAI_API_KEY.")
elif not selected_personas:
st.warning("Please select at least one persona.")
elif not (question or feature_inputs["Text"]):
st.warning("Enter a question or feature description.")
else:
if question:
st.session_state.conversation_history += f"\n**User:** {question}\n"
with st.spinner("Generating persona responses..."):
try:
resp = generate_response_with_retry(feature_inputs, selected_personas, st.session_state.conversation_history, model_choice)
st.session_state.conversation_history += resp + "\n"
st.rerun()
except Exception as e:
st.error(f"Failed to generate response: {e}")
if report_btn:
if not st.session_state.conversation_history.strip():
st.warning("Nothing to analyze yet.")
else:
with st.spinner("Generating feedback report..."):
try:
report = generate_feedback_report(st.session_state.conversation_history, model_choice)
st.markdown("## π Feedback Report")
st.markdown(report)
st.download_button("β¬οΈ Download Report", report, "persona_report.md")
except Exception as e:
st.error(f"Failed to generate report: {e}")
if clear_btn:
st.session_state.conversation_history = ""
st.rerun()
st.markdown("---")
# -------------------------
# Conversation display + heatmap
# -------------------------
st.header("π¬ Conversation History")
if st.session_state.conversation_history.strip() and selected_personas:
lines = [ln for ln in st.session_state.conversation_history.split("\n") if ln.strip()]
# Display conversation lines with persona formatting
for line in lines:
matched = False
for p in selected_personas:
if line.startswith(p["name"]):
response_text = extract_persona_response(line)
hl = detect_insight_or_concern(response_text)
st.markdown(format_response_line(line, p["name"], hl), unsafe_allow_html=True)
matched = True
break
if not matched:
st.markdown(line)
st.info("π‘ Continue the discussion using the **question field above** to ask another question.")
# Build & show heatmap
df_summary = build_sentiment_summary(lines, selected_personas)
chart = build_heatmap_chart(df_summary)
st.markdown("## π₯ Persona Sentiment Heatmap")
st.altair_chart(chart, use_container_width=True)
else:
st.info("π‘ No conversation yet. Ask your personas a question to get started!")
# -------------------------
# Sidebar persona creation (persist)
# -------------------------
st.sidebar.markdown("---")
st.sidebar.header("β Create Persona")
with st.sidebar.form("new_persona_form"):
name = st.text_input("Name*")
occupation = st.text_input("Occupation*")
location = st.text_input("Location")
tech = st.selectbox("Tech Proficiency", ["Low", "Medium", "High"])
traits = st.text_area("Behavioral traits (comma-separated)")
submit = st.form_submit_button("Add Persona")
if submit:
if not name or not occupation:
st.sidebar.error("Name and Occupation required.")
else:
new_p = {
"id": f"p{len(personas)+1}",
"name": name.strip(),
"occupation": occupation.strip(),
"location": location.strip() or "Unknown",
"tech_proficiency": tech,
"behavioral_traits": [t.strip() for t in traits.split(",") if t.strip()]
}
personas.append(new_p)
if save_personas(personas, path=DEFAULT_PERSONA_PATH):
st.sidebar.success("β
Persona added and saved.")
else:
st.sidebar.error("β Persona added but failed to save.")
st.rerun()
st.sidebar.metric("Total Personas", len(personas))
|