""" 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): N 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"{pre_int}" 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()