Spaces:
Running
Running
Final Commit
Browse files- LICENSE +21 -0
- README.md +28 -14
- app.py +211 -0
- label_encoder.joblib +3 -0
- model.py +138 -0
- personality_profiles.json +131 -0
- personality_rf_model.joblib +3 -0
- requirements.txt +6 -0
- story_engine.py +156 -0
- tfidf_vectorizer.joblib +3 -0
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2024 PSYCHEPLOT
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
CHANGED
@@ -1,14 +1,28 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# PSYCHEPLOT
|
2 |
+
|
3 |
+
[](https://opensource.org/licenses/MIT)
|
4 |
+
|
5 |
+
Interactive storytelling application with personality analysis.
|
6 |
+
|
7 |
+
## Setup
|
8 |
+
1. Clone the repository
|
9 |
+
2. Install dependencies: `pip install -r requirements.txt`
|
10 |
+
3. Add GROQ API key to Hugging Face Spaces secrets
|
11 |
+
|
12 |
+
## Files
|
13 |
+
- `app.py`: Main Streamlit application
|
14 |
+
- `story_engine.py`: Story generation logic
|
15 |
+
- `model.py`: Personality prediction model
|
16 |
+
- `personality_profiles.json`: Personality type definitions
|
17 |
+
|
18 |
+
## Models
|
19 |
+
Make sure these files are included in the repository:
|
20 |
+
- personality_rf_model.joblib
|
21 |
+
- tfidf_vectorizer.joblib
|
22 |
+
- label_encoder.joblib
|
23 |
+
|
24 |
+
## Features
|
25 |
+
- Multiple story genres
|
26 |
+
- Dynamic story generation
|
27 |
+
- Personality analysis
|
28 |
+
- Real-time trait tracking
|
app.py
ADDED
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import os
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
from story_engine import generate_story, continue_story
|
5 |
+
from model import predict_personality
|
6 |
+
|
7 |
+
# Load environment variables and set up API key first
|
8 |
+
load_dotenv()
|
9 |
+
|
10 |
+
# Get API key - handle both local development and Hugging Face Spaces
|
11 |
+
try:
|
12 |
+
GROQ_API_KEY = st.secrets["GROQ_API_KEY"]
|
13 |
+
except:
|
14 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
15 |
+
|
16 |
+
if not GROQ_API_KEY:
|
17 |
+
st.error("GROQ API Key not found. Please check your environment configuration.")
|
18 |
+
st.stop()
|
19 |
+
|
20 |
+
os.environ["GROQ_API_KEY"] = GROQ_API_KEY
|
21 |
+
os.environ["STREAMLIT_WATCHER_TYPE"] = "none"
|
22 |
+
|
23 |
+
# Initialize session state variables
|
24 |
+
if "selected_genre" not in st.session_state:
|
25 |
+
st.session_state.selected_genre = None
|
26 |
+
if "story_history" not in st.session_state:
|
27 |
+
st.session_state.story_history = []
|
28 |
+
if "story_options" not in st.session_state:
|
29 |
+
st.session_state.story_options = []
|
30 |
+
if "choice_history" not in st.session_state:
|
31 |
+
st.session_state.choice_history = []
|
32 |
+
|
33 |
+
# Track trait counts across choices
|
34 |
+
if "trait_counts" not in st.session_state:
|
35 |
+
st.session_state.trait_counts = {
|
36 |
+
"O": 0, # Openness
|
37 |
+
"C": 0, # Conscientiousness
|
38 |
+
"E": 0, # Extraversion
|
39 |
+
"A": 0, # Agreeableness
|
40 |
+
"N": 0 # Neuroticism
|
41 |
+
}
|
42 |
+
|
43 |
+
# Track personality predictions for each step
|
44 |
+
if "personality_history" not in st.session_state:
|
45 |
+
st.session_state.personality_history = []
|
46 |
+
|
47 |
+
st.set_page_config(layout="wide", page_title="PSYCHEPLOT", page_icon="👾")
|
48 |
+
|
49 |
+
st.title("👾 PSYCHEPLOT")
|
50 |
+
st.header("Select your genre")
|
51 |
+
|
52 |
+
if st.session_state.selected_genre is None:
|
53 |
+
col1, col2, col3, col4 = st.columns(4)
|
54 |
+
with col1:
|
55 |
+
if st.button("Crime"):
|
56 |
+
st.session_state.selected_genre = "Crime"
|
57 |
+
story_segment, options = generate_story("Crime", st.session_state.story_history)
|
58 |
+
st.session_state.story_options = options
|
59 |
+
with col2:
|
60 |
+
if st.button("Comedy"):
|
61 |
+
st.session_state.selected_genre = "Comedy"
|
62 |
+
story_segment, options = generate_story("Comedy", st.session_state.story_history)
|
63 |
+
st.session_state.story_options = options
|
64 |
+
with col3:
|
65 |
+
if st.button("Dark"):
|
66 |
+
st.session_state.selected_genre = "Dark"
|
67 |
+
story_segment, options = generate_story("Dark", st.session_state.story_history)
|
68 |
+
st.session_state.story_options = options
|
69 |
+
with col4:
|
70 |
+
if st.button("Educational"):
|
71 |
+
st.session_state.selected_genre = "Educational"
|
72 |
+
story_segment, options = generate_story("Educational", st.session_state.story_history)
|
73 |
+
st.session_state.story_options = options
|
74 |
+
|
75 |
+
if "step_count" not in st.session_state:
|
76 |
+
st.session_state.step_count = 0
|
77 |
+
if "chosen_options" not in st.session_state:
|
78 |
+
st.session_state.chosen_options = []
|
79 |
+
|
80 |
+
def update_personality_traits(choice_text):
|
81 |
+
"""Update trait counts based on the current choice"""
|
82 |
+
result = predict_personality(choice_text)
|
83 |
+
st.session_state.personality_history.append(result)
|
84 |
+
|
85 |
+
# Custom thresholds for each trait
|
86 |
+
thresholds = {
|
87 |
+
"O": 0.20, # Openness
|
88 |
+
"C": 0.20, # Conscientiousness
|
89 |
+
"E": 0.15, # Extraversion
|
90 |
+
"A": 0.15, # Agreeableness
|
91 |
+
"N": 0.30 # Higher threshold for Neuroticism
|
92 |
+
}
|
93 |
+
|
94 |
+
# Update trait counts with custom thresholds
|
95 |
+
for trait, score in result["traits"].items():
|
96 |
+
if score >= thresholds[trait]:
|
97 |
+
st.session_state.trait_counts[trait] += 1
|
98 |
+
|
99 |
+
return result
|
100 |
+
|
101 |
+
def get_final_personality():
|
102 |
+
"""Calculate final personality based on accumulated trait counts"""
|
103 |
+
from model import PERSONALITY_MAP, DOMINANT_TYPES
|
104 |
+
traits = ["O", "C", "E", "A", "N"]
|
105 |
+
trait_scores = {}
|
106 |
+
|
107 |
+
# Calculate scores
|
108 |
+
for trait in traits:
|
109 |
+
count = st.session_state.trait_counts[trait]
|
110 |
+
score = count / st.session_state.step_count
|
111 |
+
trait_scores[trait] = score
|
112 |
+
|
113 |
+
# Find potential dominant traits
|
114 |
+
max_score = max(trait_scores.values())
|
115 |
+
dominant_candidates = [
|
116 |
+
trait for trait, score in trait_scores.items()
|
117 |
+
if abs(score - max_score) < 0.01
|
118 |
+
]
|
119 |
+
|
120 |
+
# Check if there's a clear dominant trait
|
121 |
+
if len(dominant_candidates) == 1:
|
122 |
+
dominant_trait = dominant_candidates[0]
|
123 |
+
other_scores = [score for trait, score in trait_scores.items()
|
124 |
+
if trait != dominant_trait]
|
125 |
+
max_other = max(other_scores)
|
126 |
+
|
127 |
+
if max_score >= 0.5 and (max_score - max_other) >= 0.2:
|
128 |
+
profile = DOMINANT_TYPES[dominant_trait]
|
129 |
+
return {
|
130 |
+
"type": dominant_trait,
|
131 |
+
"category": "Dominant Trait",
|
132 |
+
"label": profile["label"],
|
133 |
+
"description": profile["description"],
|
134 |
+
"traits": trait_scores
|
135 |
+
}
|
136 |
+
|
137 |
+
# If no clear dominant trait, create binary code and use personality map
|
138 |
+
binary_code = "".join(["H" if trait_scores[trait] > 0.3 else "L"
|
139 |
+
for trait in traits])
|
140 |
+
|
141 |
+
profile = PERSONALITY_MAP.get(binary_code, {
|
142 |
+
"label": "Unique Profile",
|
143 |
+
"description": "A distinctive combination of personality traits that creates a unique character profile."
|
144 |
+
})
|
145 |
+
|
146 |
+
return {
|
147 |
+
"type": binary_code,
|
148 |
+
"category": "Mixed Profile",
|
149 |
+
"label": profile["label"],
|
150 |
+
"description": profile["description"],
|
151 |
+
"traits": trait_scores
|
152 |
+
}
|
153 |
+
|
154 |
+
if st.session_state.selected_genre:
|
155 |
+
st.subheader(f"📖 Story Begins ({st.session_state.selected_genre} story):")
|
156 |
+
for i, segment in enumerate(st.session_state.story_history):
|
157 |
+
cleaned_segment = "\n".join([line for line in segment.split("\n") if not line.startswith(("1.", "2.", "3.", "4."))])
|
158 |
+
st.write(cleaned_segment)
|
159 |
+
if i < len(st.session_state.story_history) - 1:
|
160 |
+
st.divider()
|
161 |
+
|
162 |
+
if st.session_state.step_count < 6:
|
163 |
+
st.subheader("🔮 Choose the next step:")
|
164 |
+
for option in st.session_state.story_options:
|
165 |
+
if st.button(option):
|
166 |
+
story_segment, options = continue_story(st.session_state.story_history, option)
|
167 |
+
st.session_state.story_options = options
|
168 |
+
|
169 |
+
# Store the choice
|
170 |
+
st.session_state.choice_history.append({
|
171 |
+
"step": st.session_state.step_count + 1,
|
172 |
+
"chosen_option": option
|
173 |
+
})
|
174 |
+
st.session_state.chosen_options.append(option)
|
175 |
+
|
176 |
+
# Update personality prediction after this choice
|
177 |
+
choice_text = f"Step {st.session_state.step_count + 1}: {option}"
|
178 |
+
update_personality_traits(choice_text)
|
179 |
+
|
180 |
+
st.session_state.step_count += 1
|
181 |
+
st.rerun()
|
182 |
+
else:
|
183 |
+
# Final personality calculation based on accumulated trait counts
|
184 |
+
result = get_final_personality()
|
185 |
+
|
186 |
+
st.subheader("🧠 Final Personality Assessment:")
|
187 |
+
st.markdown(f"**Type:** `{result['type']}` ({result['category']})")
|
188 |
+
st.markdown(f"**Label:** {result['label']}")
|
189 |
+
st.markdown("**Description:**")
|
190 |
+
st.write(result['description'])
|
191 |
+
|
192 |
+
st.divider()
|
193 |
+
st.markdown("### 📊 Trait Breakdown")
|
194 |
+
for trait, score in result["traits"].items():
|
195 |
+
count = st.session_state.trait_counts[trait]
|
196 |
+
st.progress(score, text=f"{trait}: {score:.2f} ({count}/{st.session_state.step_count} choices)")
|
197 |
+
|
198 |
+
# Show choices
|
199 |
+
st.divider()
|
200 |
+
with st.expander("📜 Your Story Choices"):
|
201 |
+
choices_text = "\n".join([f"Step {choice['step']}: {choice['chosen_option']}" for choice in st.session_state.choice_history])
|
202 |
+
st.text(choices_text)
|
203 |
+
|
204 |
+
# Show personality evolution
|
205 |
+
with st.expander("📈 Personality Evolution"):
|
206 |
+
for i, personality in enumerate(st.session_state.personality_history):
|
207 |
+
st.markdown(f"**Step {i+1}**: {personality['type']} - {personality['label']}")
|
208 |
+
cols = st.columns(5)
|
209 |
+
for j, (trait, score) in enumerate(personality['traits'].items()):
|
210 |
+
with cols[j]:
|
211 |
+
st.progress(score, text=f"{trait}: {score:.2f}")
|
label_encoder.joblib
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:55e7a831ac825d0ba41c0e985ab58cb8127232091c76d229de76d1197398e5ae
|
3 |
+
size 615
|
model.py
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import joblib
|
2 |
+
import json
|
3 |
+
import re
|
4 |
+
|
5 |
+
# Load the models
|
6 |
+
rf_model = joblib.load("personality_rf_model.joblib")
|
7 |
+
vectorizer = joblib.load("tfidf_vectorizer.joblib")
|
8 |
+
label_encoder = joblib.load("label_encoder.joblib")
|
9 |
+
|
10 |
+
# Load personality profiles
|
11 |
+
with open("personality_profiles.json", "r") as f:
|
12 |
+
PERSONALITY_MAP = json.load(f)
|
13 |
+
|
14 |
+
# Define dominant types
|
15 |
+
DOMINANT_TYPES = {
|
16 |
+
"O": {
|
17 |
+
"label": "High Openness",
|
18 |
+
"description": "Curious, imaginative, and open to new experiences. Thrives in creative and unconventional environments."
|
19 |
+
},
|
20 |
+
"C": {
|
21 |
+
"label": "High Conscientiousness",
|
22 |
+
"description": "Organized, dependable, and goal-oriented. Strong sense of duty and self-discipline."
|
23 |
+
},
|
24 |
+
"E": {
|
25 |
+
"label": "High Extraversion",
|
26 |
+
"description": "Energetic, outgoing, and thrives on social interaction. Feeds off external stimulation."
|
27 |
+
},
|
28 |
+
"A": {
|
29 |
+
"label": "High Agreeableness",
|
30 |
+
"description": "Kind-hearted, empathetic, and cooperative. Seeks harmony and avoids conflict."
|
31 |
+
},
|
32 |
+
"N": {
|
33 |
+
"label": "High Neuroticism",
|
34 |
+
"description": "Emotionally sensitive, reactive, and prone to mood swings. Deeply introspective."
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
def preprocess_text(text):
|
39 |
+
"""Preprocess text for prediction"""
|
40 |
+
text = text.lower()
|
41 |
+
text = re.sub(r'[^a-zA-Z\s]', '', text)
|
42 |
+
text = re.sub(r'\s+', ' ', text)
|
43 |
+
return text.strip()
|
44 |
+
|
45 |
+
def predict_personality(choices_text):
|
46 |
+
"""Predict personality based on choices text"""
|
47 |
+
try:
|
48 |
+
# Extract choice numbers from the text
|
49 |
+
choices = re.findall(r'Step \d+: (\d+)\.', choices_text)
|
50 |
+
|
51 |
+
# Define trait mapping for each option number
|
52 |
+
trait_mapping = {
|
53 |
+
"1": "O", # Openness
|
54 |
+
"2": "C", # Conscientiousness
|
55 |
+
"3": "E", # Extraversion/Agreeableness
|
56 |
+
"4": "N" # Neuroticism
|
57 |
+
}
|
58 |
+
|
59 |
+
# Initialize trait counts
|
60 |
+
trait_counts = {"O": 0, "C": 0, "E": 0, "A": 0, "N": 0}
|
61 |
+
total_choices = len(choices)
|
62 |
+
|
63 |
+
# Count traits based on choices
|
64 |
+
for choice in choices:
|
65 |
+
if choice in trait_mapping:
|
66 |
+
trait = trait_mapping[choice]
|
67 |
+
trait_counts[trait] += 1
|
68 |
+
# Special case: option 3 contributes to both E and A
|
69 |
+
if choice == "3":
|
70 |
+
trait_counts["A"] += 0.5 # Reduced weight for secondary trait
|
71 |
+
|
72 |
+
# Calculate trait scores as percentages
|
73 |
+
trait_scores = {
|
74 |
+
trait: count/total_choices
|
75 |
+
for trait, count in trait_counts.items()
|
76 |
+
} if total_choices > 0 else trait_counts
|
77 |
+
|
78 |
+
# Find dominant trait(s) with stricter criteria
|
79 |
+
max_score = max(trait_scores.values())
|
80 |
+
|
81 |
+
# Count traits that share the max score
|
82 |
+
max_score_traits = [
|
83 |
+
trait for trait, score in trait_scores.items()
|
84 |
+
if abs(score - max_score) < 0.01 # Account for floating point comparison
|
85 |
+
]
|
86 |
+
|
87 |
+
# Only consider dominant if:
|
88 |
+
# 1. Single trait has highest score
|
89 |
+
# 2. Score is significantly higher than others (>= 0.5)
|
90 |
+
# 3. No other trait is close to the max score
|
91 |
+
if len(max_score_traits) == 1 and max_score >= 0.5:
|
92 |
+
dominant_trait = max_score_traits[0]
|
93 |
+
other_scores = [score for trait, score in trait_scores.items()
|
94 |
+
if trait != dominant_trait]
|
95 |
+
max_other = max(other_scores) if other_scores else 0
|
96 |
+
|
97 |
+
if max_score - max_other >= 0.2: # Must be significantly higher
|
98 |
+
profile = DOMINANT_TYPES[dominant_trait]
|
99 |
+
return {
|
100 |
+
"type": dominant_trait,
|
101 |
+
"category": "Dominant Trait",
|
102 |
+
"label": profile["label"],
|
103 |
+
"description": profile["description"],
|
104 |
+
"traits": trait_scores
|
105 |
+
}
|
106 |
+
|
107 |
+
# If no clear dominant trait, use binary code
|
108 |
+
binary_code = "".join(["H" if trait_scores[trait] > 0.3 else "L"
|
109 |
+
for trait in ["O", "C", "E", "A", "N"]]) # Fixed order
|
110 |
+
|
111 |
+
# Get profile from personality map
|
112 |
+
profile = PERSONALITY_MAP.get(binary_code, {
|
113 |
+
"label": "Mixed Profile",
|
114 |
+
"description": "A balanced combination of different personality traits."
|
115 |
+
})
|
116 |
+
|
117 |
+
return {
|
118 |
+
"type": binary_code,
|
119 |
+
"category": "Mixed Profile",
|
120 |
+
"label": profile["label"],
|
121 |
+
"description": profile["description"],
|
122 |
+
"traits": trait_scores
|
123 |
+
}
|
124 |
+
|
125 |
+
except Exception as e:
|
126 |
+
print(f"Error during prediction: {e}")
|
127 |
+
return {
|
128 |
+
"type": "ERROR",
|
129 |
+
"category": "Error",
|
130 |
+
"label": "Prediction Error",
|
131 |
+
"description": str(e),
|
132 |
+
"traits": {}
|
133 |
+
}
|
134 |
+
|
135 |
+
# if __name__ == "__main__":
|
136 |
+
# example_text = "I enjoy meeting new people and trying new experiences"
|
137 |
+
# result = predict_personality(example_text)
|
138 |
+
# print(json.dumps(result, indent=2))
|
personality_profiles.json
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"HHHHH": {
|
3 |
+
"label": "The Enlightened Leader",
|
4 |
+
"description": "Curious, disciplined, outgoing, kind, and emotionally aware. A natural leader with high emotional intelligence."
|
5 |
+
},
|
6 |
+
"HHHHL": {
|
7 |
+
"label": "The Visionary Diplomat",
|
8 |
+
"description": "Inventive, reliable, socially skilled, cooperative, and emotionally stable. Great at guiding others and building bridges."
|
9 |
+
},
|
10 |
+
"HHHLH": {
|
11 |
+
"label": "The Passionate Performer",
|
12 |
+
"description": "Creative, organized, charismatic, assertive, and emotionally expressive. Thrives in dynamic, public environments."
|
13 |
+
},
|
14 |
+
"HHHLL": {
|
15 |
+
"label": "The Independent Idealist",
|
16 |
+
"description": "Imaginative and structured, yet introverted and blunt. Strives for truth over harmony."
|
17 |
+
},
|
18 |
+
"HHLHH": {
|
19 |
+
"label": "The Charismatic Reactor",
|
20 |
+
"description": "Thoughtful and unconventional, struggles with emotional turbulence but excels at inspiring others."
|
21 |
+
},
|
22 |
+
"HHLHL": {
|
23 |
+
"label": "The Unpredictable Artist",
|
24 |
+
"description": "Highly creative and principled, but emotionally steady and often distant. Expresses through abstract forms."
|
25 |
+
},
|
26 |
+
"HHLLH": {
|
27 |
+
"label": "The Analytical Rebel",
|
28 |
+
"description": "Innovative and introverted, resistant to rules but deeply sensitive inside."
|
29 |
+
},
|
30 |
+
"HHLLL": {
|
31 |
+
"label": "The Detached Visionary",
|
32 |
+
"description": "Independent, logical, and emotionally distant. Values thought over connection."
|
33 |
+
},
|
34 |
+
"HLHHH": {
|
35 |
+
"label": "The Fiery Advocate",
|
36 |
+
"description": "Empathetic yet impulsive. Low on structure but high on social drive and emotional intensity."
|
37 |
+
},
|
38 |
+
"HLHHL": {
|
39 |
+
"label": "The Vibrant Connector",
|
40 |
+
"description": "Warm, spontaneous, and full of life. Prefers chaos over routine but deeply values harmony."
|
41 |
+
},
|
42 |
+
"HLHLH": {
|
43 |
+
"label": "The Empathetic Strategist",
|
44 |
+
"description": "Creative and emotionally insightful, yet assertive and unafraid of conflict. Balances heart and head."
|
45 |
+
},
|
46 |
+
"HLHLL": {
|
47 |
+
"label": "The Expressive Maverick",
|
48 |
+
"description": "Energetic and persuasive, but emotionally stable and unapologetically individualistic."
|
49 |
+
},
|
50 |
+
"HLLHH": {
|
51 |
+
"label": "The Sensitive Idealist",
|
52 |
+
"description": "Kind-hearted and emotionally intense, yet often disorganized and introspective."
|
53 |
+
},
|
54 |
+
"HLLHL": {
|
55 |
+
"label": "The Intuitive Wanderer",
|
56 |
+
"description": "Unconventional and curious with a soft demeanor. Dislikes routine and avoids conflict."
|
57 |
+
},
|
58 |
+
"HLLLH": {
|
59 |
+
"label": "The Lone Visionary",
|
60 |
+
"description": "Quiet, original, sensitive, and slightly chaotic. Expresses through solitude and deep thought."
|
61 |
+
},
|
62 |
+
"HLLLL": {
|
63 |
+
"label": "The Elusive Thinker",
|
64 |
+
"description": "Detached from structure and low in sociability, yet highly imaginative and philosophical."
|
65 |
+
},
|
66 |
+
"LHHHH": {
|
67 |
+
"label": "The Loyal Realist",
|
68 |
+
"description": "Grounded, dependable, and sensitive. Seeks harmony and stability in a chaotic world."
|
69 |
+
},
|
70 |
+
"LHHHL": {
|
71 |
+
"label": "The Social Strategist",
|
72 |
+
"description": "Disciplined, sociable, and emotionally grounded. Excels in managing group dynamics."
|
73 |
+
},
|
74 |
+
"LHHLH": {
|
75 |
+
"label": "The Compassionate Achiever",
|
76 |
+
"description": "Structured, ambitious, and sensitive to others’ needs. Driven by purpose and empathy."
|
77 |
+
},
|
78 |
+
"LHHLL": {
|
79 |
+
"label": "The Grounded Realist",
|
80 |
+
"description": "Practical and persistent. Values results, avoids drama, and prefers predictability."
|
81 |
+
},
|
82 |
+
"LHLHH": {
|
83 |
+
"label": "The Expressive Sentinel",
|
84 |
+
"description": "Emotionally in tune and supportive, but avoids chaos and prefers routine."
|
85 |
+
},
|
86 |
+
"LHLHL": {
|
87 |
+
"label": "The Warm Minimalist",
|
88 |
+
"description": "Reserved and steady, but deeply caring. Keeps things simple and values deep connections."
|
89 |
+
},
|
90 |
+
"LHLLH": {
|
91 |
+
"label": "The Pragmatic Thinker",
|
92 |
+
"description": "Introverted but deeply rational. Balanced emotionality and a strong moral compass."
|
93 |
+
},
|
94 |
+
"LHLLL": {
|
95 |
+
"label": "The Structured Observer",
|
96 |
+
"description": "Detail-oriented and organized, with a tendency toward solitude."
|
97 |
+
},
|
98 |
+
"LLHHH": {
|
99 |
+
"label": "The Tender Explorer",
|
100 |
+
"description": "Emotionally rich and adventurous, but often lacks follow-through. Guided by heart and curiosity."
|
101 |
+
},
|
102 |
+
"LLHHL": {
|
103 |
+
"label": "The Relational Drifter",
|
104 |
+
"description": "Compassionate and engaging, but scattered. Thrives in the moment, avoids hard rules."
|
105 |
+
},
|
106 |
+
"LLHLH": {
|
107 |
+
"label": "The Emotional Realist",
|
108 |
+
"description": "Feels deeply and sees clearly. Honest and loyal, but struggles with emotional regulation."
|
109 |
+
},
|
110 |
+
"LLHLL": {
|
111 |
+
"label": "The Gentle Disruptor",
|
112 |
+
"description": "Quietly rebellious, kind, and skeptical of systems. Doesn’t like to be boxed in."
|
113 |
+
},
|
114 |
+
"LLLHH": {
|
115 |
+
"label": "The Chaotic Thinker",
|
116 |
+
"description": "Unstructured, sensitive, and highly perceptive. Often lost in thought or feeling."
|
117 |
+
},
|
118 |
+
"LLLHL": {
|
119 |
+
"label": "The Quiet Helper",
|
120 |
+
"description": "Introverted and steady, avoids confrontation. Gentle and observant, a calming presence."
|
121 |
+
},
|
122 |
+
"LLLLH": {
|
123 |
+
"label": "The Internal Storm",
|
124 |
+
"description": "Low in structure and sociability, but emotionally intense. Often misunderstood but deeply insightful."
|
125 |
+
},
|
126 |
+
"LLLLL": {
|
127 |
+
"label": "The Detached Observer",
|
128 |
+
"description": "Low across the board. Independent and rational, but emotionally distant and withdrawn."
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
personality_rf_model.joblib
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:e1a8e8bca637b5fbf193ed70ca19b5deb38aa2248a98c426b3cc095d69f81fc3
|
3 |
+
size 11635057
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
groq
|
3 |
+
python-dotenv
|
4 |
+
joblib
|
5 |
+
scikit-learn
|
6 |
+
watchdog
|
story_engine.py
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import os
|
3 |
+
from groq import Groq
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
import streamlit as st
|
6 |
+
|
7 |
+
# Load environment variables
|
8 |
+
load_dotenv()
|
9 |
+
|
10 |
+
# Get API key - handle both local development and Hugging Face Spaces
|
11 |
+
try:
|
12 |
+
# Try to get from Streamlit secrets (Hugging Face Spaces)
|
13 |
+
GROQ_API_KEY = st.secrets["GROQ_API_KEY"]
|
14 |
+
except:
|
15 |
+
# Fallback to local .env file
|
16 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
17 |
+
|
18 |
+
if not GROQ_API_KEY:
|
19 |
+
raise Exception("GROQ API Key not found. Please check your environment configuration.")
|
20 |
+
|
21 |
+
# Initialize Groq client
|
22 |
+
client = Groq(api_key=GROQ_API_KEY)
|
23 |
+
|
24 |
+
# Different story starts for each genre
|
25 |
+
GENRE_STARTS = {
|
26 |
+
"Crime": "It's 11:00 pm on a winter night, you are standing next to a phone booth, you are witnessing a murder scene, and then the murderers see you standing behind the booth and watching the scene.",
|
27 |
+
"Comedy": "You accidentally walk into a top-secret government meeting, but instead of being arrested, they mistake you for their new intern. Now, you must pretend to know what you're doing.",
|
28 |
+
"Dark": "The last thing you remember is falling asleep in your bed, but now you wake up in an abandoned hospital with flickering lights and a note in your hand that says 'RUN'.",
|
29 |
+
"Educational": "A renowned scientist just discovered a way to time travel, and you're the first volunteer to test the machine. However, something goes wrong, and you end up in the year 3024."
|
30 |
+
}
|
31 |
+
|
32 |
+
def generate_story(genre, story_history):
|
33 |
+
try:
|
34 |
+
start_story = GENRE_STARTS.get(genre, "A new adventure begins...")
|
35 |
+
prompt = f"""
|
36 |
+
Write a story continuation and provide 4 options that map to specific personality traits:
|
37 |
+
Story start: {start_story}
|
38 |
+
|
39 |
+
Format:
|
40 |
+
[STORY]
|
41 |
+
Write the story continuation here (max 150 words)
|
42 |
+
|
43 |
+
[OPTIONS]
|
44 |
+
1. An option showing openness (creative, exploratory approach)
|
45 |
+
2. An option showing conscientiousness (methodical, careful approach)
|
46 |
+
3. An option showing extraversion and agreeableness (social, cooperative approach)
|
47 |
+
4. An option showing neuroticism (emotional, cautious approach)
|
48 |
+
|
49 |
+
Important: Present the options naturally without mentioning these traits explicitly.
|
50 |
+
Each option should be a natural story choice while subtly reflecting its trait.
|
51 |
+
"""
|
52 |
+
|
53 |
+
response = client.chat.completions.create(
|
54 |
+
messages=[{"role": "user", "content": prompt}],
|
55 |
+
model="meta-llama/llama-4-scout-17b-16e-instruct",
|
56 |
+
temperature=0.7,
|
57 |
+
max_tokens=800
|
58 |
+
)
|
59 |
+
|
60 |
+
content = response.choices[0].message.content
|
61 |
+
|
62 |
+
# Split content into story and options
|
63 |
+
parts = content.split("[OPTIONS]")
|
64 |
+
if len(parts) == 2:
|
65 |
+
story = parts[0].replace("[STORY]", "").strip()
|
66 |
+
options_text = parts[1].strip()
|
67 |
+
options = [line.strip() for line in options_text.split("\n") if line.strip().startswith(("1.", "2.", "3.", "4."))]
|
68 |
+
else:
|
69 |
+
story = content
|
70 |
+
options = ["Continue the story", "Ask for help", "Take a different path", "Wait and observe"]
|
71 |
+
|
72 |
+
story_history.append(story)
|
73 |
+
return story, options
|
74 |
+
|
75 |
+
except Exception as e:
|
76 |
+
print(f"Error generating story: {e}")
|
77 |
+
return "An error occurred while generating the story. Please try again.", ["Retry", "Choose different genre", "Start over", "Get help"]
|
78 |
+
|
79 |
+
def continue_story(story_history, selected_option):
|
80 |
+
try:
|
81 |
+
full_story = "\n".join(story_history)
|
82 |
+
prompt = f"""
|
83 |
+
Previous story: {full_story}
|
84 |
+
Chosen action: {selected_option}
|
85 |
+
|
86 |
+
Continue the story based on the chosen action.
|
87 |
+
Provide 4 new options that map to:
|
88 |
+
- Option 1: Openness (innovative, creative solutions)
|
89 |
+
- Option 2: Conscientiousness (careful planning, responsibility)
|
90 |
+
- Option 3: Extraversion/Agreeableness (social interaction, cooperation)
|
91 |
+
- Option 4: Neuroticism (emotional awareness, caution)
|
92 |
+
|
93 |
+
Format:
|
94 |
+
[STORY]
|
95 |
+
Write the continuation here (max 150 words)
|
96 |
+
|
97 |
+
[OPTIONS]
|
98 |
+
Present 4 natural story options that subtly reflect these traits without explicitly mentioning them.
|
99 |
+
Make each option feel like a natural choice in the story.
|
100 |
+
"""
|
101 |
+
|
102 |
+
response = client.chat.completions.create(
|
103 |
+
model="meta-llama/llama-4-scout-17b-16e-instruct",
|
104 |
+
messages=[{"role": "user", "content": prompt}],
|
105 |
+
temperature=0.7,
|
106 |
+
max_tokens=800
|
107 |
+
)
|
108 |
+
|
109 |
+
content = response.choices[0].message.content
|
110 |
+
|
111 |
+
# Split content into story and options
|
112 |
+
parts = content.split("[OPTIONS]")
|
113 |
+
if len(parts) == 2:
|
114 |
+
story = parts[0].replace("[STORY]", "").strip()
|
115 |
+
options_text = parts[1].strip()
|
116 |
+
options = [line.strip() for line in options_text.split("\n") if line.strip().startswith(("1.", "2.", "3.", "4."))]
|
117 |
+
else:
|
118 |
+
story = content
|
119 |
+
options = ["Continue forward", "Take a different approach", "Reconsider the situation", "Try something new"]
|
120 |
+
|
121 |
+
story_history.append(story)
|
122 |
+
return story, options
|
123 |
+
|
124 |
+
except Exception as e:
|
125 |
+
print(f"Error continuing story: {e}")
|
126 |
+
return "An error occurred while continuing the story. Please try again.", ["Retry", "Go back", "Choose different option", "Start over"]
|
127 |
+
|
128 |
+
# def predict_personality(choices_text):
|
129 |
+
# prompt = f"""
|
130 |
+
# Analyze the following choices made by the user and determine their personality type:
|
131 |
+
|
132 |
+
# Choices:
|
133 |
+
# {choices_text}
|
134 |
+
|
135 |
+
# The choices are labeled as follows:
|
136 |
+
# 1. Emotional
|
137 |
+
# 2. Rational
|
138 |
+
# 3. Diplomatic
|
139 |
+
# 4. Angry
|
140 |
+
|
141 |
+
# Based on the frequency and pattern of choices, predict the user's personality type.
|
142 |
+
# Give a short summary of their personality traits.
|
143 |
+
|
144 |
+
# ### Give me only output
|
145 |
+
# """
|
146 |
+
|
147 |
+
# response = client.chat.completions.create(
|
148 |
+
# model="llama3-70b-8192",
|
149 |
+
# messages=[{"role": "user", "content": prompt}],
|
150 |
+
# temperature=0.3,
|
151 |
+
# max_tokens=300
|
152 |
+
# )
|
153 |
+
|
154 |
+
# return response.choices[0].message.content
|
155 |
+
|
156 |
+
|
tfidf_vectorizer.joblib
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:bdcdf34dc746af15d6a03516ab5e655e7a3a9b4b05508d1fe3bf7b7bc11cb1d3
|
3 |
+
size 196257
|