import re import gradio as gr from datasets import load_dataset from huggingface_hub import InferenceClient from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 1. Connect to Hugging Face Inference API (free hosted models) # You MUST add your Hugging Face token in the Space "Secrets" panel as HF_TOKEN client = InferenceClient("microsoft/phi-2", token=None) # if hosted on your HF account, no token needed def generate_text(prompt, max_tokens=150): """Call Hugging Face Inference API to generate text.""" response = client.text_generation(prompt, max_new_tokens=max_tokens) return response # 2. Load dataset for RAG data = load_dataset("Amod/mental_health_counseling_conversations") docs = [f"Q: {entry['Context']}\nA: {entry['Response']}" for entry in data['train']] embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") vectordb = Chroma.from_texts(texts=docs, embedding=embedding_model) # 3. Traits dictionary traits = {"Secure": 0, "Anxious": 0, "Avoidant": 0} MAX_SCENES = 5 # Narrative instructions for LLM narrator_instructions = ( "You are a supportive narrative AI creating an interactive story for the player.\n" "Write in second person (\"you\" as Alex) and describe feelings and events vividly.\n" "The story should feel emotionally deep and realistic, suitable for an adult audience (age 25-35).\n" "Focus on how attachment styles influence Alex's feelings and decisions in a romantic relationship.\n" "In each scene, after narrating the situation, offer the player choices labeled A), B), (and possibly C)).\n" "Each choice should represent a different attachment response (secure, avoidant, anxious).\n" "Do NOT reveal the outcome of the choices yet; just present the options.\n" ) # --- STORY FUNCTIONS --- def parse_choices(text): """Split LLM output into narrative and choices.""" choice_start = text.find("A)") if choice_start == -1: return text, [] narrative = text[:choice_start].strip() choice_lines = text[choice_start:].splitlines() choices = [line.strip() for line in choice_lines if re.match(r"^[A-C]\)", line.strip())] return narrative, choices def generate_initial_scene(): intro = ( "Alex is a 26-year-old graduate student who recently moved to the UK. " "They are in a romantic relationship, but lately Alex feels insecure. " "One morning, Alex wakes up with a knot in their stomach. " "Their partner didn’t reply to a message from last night. " "Thoughts race through Alex’s mind..." ) text = generate_text(narrator_instructions + intro + "\nWhat does Alex do?") return parse_choices(text) def next_step(selected_choice, story_so_far, scene_index, traits_dict): if not selected_choice: return story_so_far, [], scene_index, traits_dict choice_label = selected_choice[0] choice_text = selected_choice[3:].strip() scene_index += 1 # Update traits text_lower = choice_text.lower() if any(word in text_lower for word in ["talk", "share", "open", "honest", "calm"]): traits_dict["Secure"] += 1 if any(word in text_lower for word in ["ignore", "avoid", "distance", "later", "alone"]): traits_dict["Avoidant"] += 1 if any(word in text_lower for word in ["worry", "jealous", "clingy", "panic", "angry", "upset"]): traits_dict["Anxious"] += 1 # Retrieve advice results = vectordb.similarity_search(choice_text + " relationship anxiety", k=1) advice = results[0].page_content.split("A:")[1].strip() if results else "" # Prompt for next scene last_line = story_so_far.splitlines()[-1] if story_so_far else "" prompt = narrator_instructions prompt += f"Previously: {last_line}\n" prompt += f"The player chose option {choice_label}: {choice_text}.\n" if advice: prompt += f"[Helpful thought: {advice}]\n" prompt += "Continue the story and provide new choices.\n" text = generate_text(prompt) return *parse_choices(text), scene_index, traits_dict def summarize_story(story, traits_dict): prompt = ( f"Alex's story is ending. Traits: Secure={traits_dict['Secure']}, " f"Anxious={traits_dict['Anxious']}, Avoidant={traits_dict['Avoidant']}.\n" "Write a reflective ending about what Alex learned about relationships." ) return generate_text(prompt, max_tokens=120) # --- GRADIO UI --- with gr.Blocks(css=".gradio-container {background-color:#f5f7fa;} #story_box {padding:15px; background:#fff; border-radius:10px;}") as demo: story_state = gr.State("") scene_state = gr.State(0) traits_state = gr.State(traits.copy()) gr.HTML("

💙 Attachment Style Interactive Story

Explore how attachment styles shape relationships.

") # Background music gr.HTML(""" """) story_box = gr.Markdown("*(Loading story...)*", elem_id="story_box") choices_radio = gr.Radio(label="Choose:", choices=[], visible=False) next_btn = gr.Button("Next") def start_game(): narrative, choices = generate_initial_scene() return narrative, choices, 1, {"Secure":0,"Anxious":0,"Avoidant":0} def play_step(choice, story, scene, traits_dict): if scene >= MAX_SCENES: return story + "\n\n" + summarize_story(story, traits_dict), [], scene, traits_dict narrative, choices, new_scene, traits_dict = next_step(choice, story, scene, traits_dict) story += "\n\n" + narrative if new_scene >= MAX_SCENES: story += "\n\n" + summarize_story(story, traits_dict) return story, [], new_scene, traits_dict return story, choices, new_scene, traits_dict demo.load(start_game, inputs=None, outputs=[story_box, choices_radio, scene_state, traits_state]) next_btn.click(play_step, inputs=[choices_radio, story_state, scene_state, traits_state], outputs=[story_box, choices_radio, scene_state, traits_state]) demo.launch()