prolific_preferences / src /ui /screens_preference.py
ehejin's picture
0505 np3 prolfiic
d1c998c
"""
Preference study β€” pair introduction screen.
Participants see Product A and Product B, rate familiarity for both,
choose an initial preference (1–7), and click "Start Chat".
Clicking "Start Chat" initialises the conversation:
1. AI (synthetic): preference_initial(pair_overview)
2. User (synthetic): <choice>N</choice> where N = pre_rating integer
3. AI (model): first persuasion response
The seller always argues for Product A regardless of the participant's lean.
"""
import time
import streamlit as st
from src.model import call_model
from src.lsp_wrappers import (
build_seller_system_prompt_preference,
closing_message_preference,
format_demographics,
opening_message_preference,
)
from src.ui.components import (
familiarity_choices,
parse_rating,
rating_choices,
render_pair_cards,
render_progress,
)
def screen_pair_intro(s: dict, cfg: dict) -> None:
idx = s["current_index"]
item = s["items"][idx]
pair_id = item["pair_id"]
render_progress(idx + 1, cfg["pairs_per_user"])
st.markdown("## Product Comparison")
st.markdown(
"Please read both products carefully before answering the questions below."
)
render_pair_cards(item)
st.markdown("---")
# ── Familiarity for Product A ─────────────────────────────────────────────
cat_a = item["product_a"].get("category", item.get("category", ""))
fam_a_opts = familiarity_choices(cat_a)
fam_a = st.radio(
f"How familiar are you with **Product A** "
f"(*{item['product_a'].get('title', '')[:60]}…*)?",
fam_a_opts,
index=None,
key=f"fam_a_{idx}_{pair_id}",
)
# ── Familiarity for Product B ─────────────────────────────────────────────
cat_b = item["product_b"].get("category", item.get("category", ""))
fam_b_opts = familiarity_choices(cat_b)
fam_b = st.radio(
f"How familiar are you with **Product B** "
f"(*{item['product_b'].get('title', '')[:60]}…*)?",
fam_b_opts,
index=None,
key=f"fam_b_{idx}_{pair_id}",
)
st.markdown("---")
# ── Initial preference rating ─────────────────────────────────────────────
choices = rating_choices("preference")
pre_val = st.radio(
"Considering these two products, which would you prefer to buy?",
choices,
index=None,
key=f"pre_rating_{idx}_{pair_id}",
)
if st.button("Start Chat β†’", type="primary", use_container_width=True):
if not cfg["debug_mode"]:
if not fam_a:
st.error("⚠️ Please rate your familiarity with Product A.")
return
if not fam_b:
st.error("⚠️ Please rate your familiarity with Product B.")
return
if not pre_val:
st.error("⚠️ Please rate your preference before starting the chat.")
return
# Defaults for debug mode
fam_a = fam_a or fam_a_opts[0]
fam_b = fam_b or fam_b_opts[0]
pre_val = pre_val or choices[3] # Neutral (4)
pre_int = parse_rating(pre_val)
# ── Per-item config (model + prompt variant assigned at session init) ─
item_cfg = {
**cfg,
"prompt_variant": item.get("prompt_variant", {}),
"model_name": item.get("model_name", ""),
"sampler_path": item.get("sampler_path", ""),
}
# Build the demographics string in whichever format the trained model expects.
# When include_bio is True we feed the participant's own background answers
# (movies_criteria / movies_enjoy / movies_avoid) the same way training does.
include_bio = bool(item_cfg["prompt_variant"].get("include_bio", False))
demo_str = format_demographics(
s["demographics"],
background=s.get("background", {}),
include_bio=include_bio,
)
# ── Build prompts ─────────────────────────────────────────────────────
system_prompt = build_seller_system_prompt_preference(item, item_cfg, demo_str)
opening_msg = opening_message_preference(item)
user_choice_msg = f"<choice>{pre_int}</choice>"
closing_msg = closing_message_preference(item) # logged only
# ── First persuasion response from the model ──────────────────────────
messages = [
{"role": "system", "content": system_prompt},
{"role": "assistant", "content": opening_msg},
{"role": "user", "content": user_choice_msg},
]
with st.spinner("Starting conversation…"):
ai_reply = call_model(messages, item_cfg)
now = time.time()
# ── Persist to state ──────────────────────────────────────────────────
s["items"][idx]["familiarity_a"] = fam_a
s["items"][idx]["familiarity_b"] = fam_b
s["items"][idx]["pre_rating"] = pre_int
s["items"][idx]["conversation"].update({
"system_prompt": system_prompt,
"closing_message": closing_msg,
"turns": [
{
"turn_index": 0,
"role": "assistant",
"content": opening_msg,
"timestamp": now,
"synthetic": True,
},
{
"turn_index": 1,
"role": "user",
"content": user_choice_msg,
"timestamp": now,
"synthetic": True,
},
{
"turn_index": 2,
"role": "assistant",
"content": ai_reply,
"timestamp": now,
"model": item_cfg["model_name"],
},
],
"num_turns": 0,
})
print(f"[CONV] num turns stored: {len(s['items'][idx]['conversation']['turns'])}")
print(f"[CONV] turn roles: {[(t['role'], t.get('synthetic')) for t in s['items'][idx]['conversation']['turns']]}")
print(f"[CONV] turn 0 content[:100]: {s['items'][idx]['conversation']['turns'][0]['content'][:100]}")
print(f"[CONV] turn 1 content: {s['items'][idx]['conversation']['turns'][1]['content']}")
s["screen"] = "chat"
st.rerun()