File size: 6,287 Bytes
eb8a45a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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("<h1>πŸ’™ Attachment Style Interactive Story</h1><p>Explore how attachment styles shape relationships.</p>")

    # Background music
    gr.HTML("""

    <audio src='file=bg_music.mp3' autoplay loop hidden></audio>

    """)

    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()