File size: 10,091 Bytes
88014f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bcdc81
88014f9
738863b
88014f9
 
 
 
 
 
 
fb5dc8c
88014f9
 
 
853b07e
88014f9
 
 
 
 
 
 
 
 
7bcdc81
88014f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6a621d6
b0fce7e
88014f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6813ba1
6a621d6
6813ba1
9843498
88014f9
9843498
 
6a621d6
fb5dc8c
88014f9
 
 
 
853b07e
88014f9
c5caf48
9513566
88014f9
c5caf48
9513566
88014f9
c5caf48
9513566
88014f9
c5caf48
88014f9
 
6813ba1
 
 
88014f9
6813ba1
 
 
88014f9
9843498
6813ba1
 
88014f9
9513566
6813ba1
 
88014f9
9513566
6813ba1
 
0ad7920
9513566
6813ba1
 
ee30a87
88014f9
 
 
6813ba1
88014f9
738863b
c5caf48
9513566
88014f9
6813ba1
 
 
738863b
9513566
88014f9
6813ba1
 
 
 
 
 
 
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# context_engine.py
"""
Model-free context & emotion heuristics.

Replaces the prior transformer-based emotion classifier with a
fast, deterministic heuristic that infers:
  - primary_emotion: one of ('joy','sadness','anger','fear','surprise','neutral')
  - emotion_confidence: float (0.0 - 1.0) indicating heuristic strength
  - conversation_mode: Ping-Pong / Standard / Deep Dive
  - emoji suggestions and min-word guidance

Design notes:
  - Uses emoji presence, punctuation, uppercase emphasis, keywords, negations,
    question density, message length, and repetition to infer emotion.
  - Intentionally conservative: returns moderate confidences unless strong signals.
  - No external libraries or model downloads required.
"""
from typing import Tuple
import re

# Keyword lists (tunable)
_JOY_KEYWORDS = {"happy", "great", "awesome", "fantastic", "nice", "love", "yay", "yay!", "cool", "amazing", "thanks", "thank you", "cheers"}
_SADNESS_KEYWORDS = {"sad", "unhappy", "depressed", "upset", "down", "sadder", "melancholy", "sorrow", "lonely"}
_ANGER_KEYWORDS = {"angry", "frustrat", "frustrated", "mad", "furious", "annoyed", "irritat", "rage", "disgusted"}
_FEAR_KEYWORDS = {"scared", "afraid", "anxious", "worried", "panic", "nervous", "fear"}
_SURPRISE_KEYWORDS = {"wow", "whoa", "surpris", "unexpected", "amazed", "shocked"}
_NEGATIONS = {"not", "don't", "didn't", "can't", "couldn't", "won't", "never", "n't"}

_EMOJI_POSITIVE = {"๐Ÿ˜Š","๐Ÿ™‚","๐Ÿ˜„","๐Ÿ˜","๐ŸŽ‰","๐Ÿ‘","๐Ÿค","๐Ÿ˜ƒ","โœจ","๐Ÿ˜"}
_EMOJI_NEGATIVE = {"๐Ÿ˜ข","๐Ÿ˜ž","โ˜น๏ธ","๐Ÿ˜ก","๐Ÿ˜ญ","๐Ÿ˜ ","๐Ÿ˜ค","๐Ÿ˜–","๐Ÿ˜ฉ","๐Ÿ˜“"}
_EMOJI_SURPRISE = {"๐Ÿ˜ฒ","๐Ÿ˜ฏ","๐Ÿ˜ฎ","๐Ÿคฏ","๐Ÿ˜ณ"}

def _count_emojis(text: str):
    # simple unicode emoji detection by ranges + common emoji symbols (lightweight)
    # also check presence in our small emoji sets
    pos = sum(1 for e in _EMOJI_POSITIVE if e in text)
    neg = sum(1 for e in _EMOJI_NEGATIVE if e in text)
    sup = sum(1 for e in _EMOJI_SURPRISE if e in text)
    # rough generic emoji count (fallback)
    generic = len(re.findall(r'[\U0001F300-\U0001FAFF\U00002700-\U000027BF]', text))
    return {"positive": pos, "negative": neg, "surprise": sup, "generic": generic}

def _word_tokens(text: str):
    return re.findall(r"\w+", text.lower())

def _keyword_score(tokens, keywords):
    return sum(1 for t in tokens if any(t.startswith(k) for k in keywords))

def _has_upper_emphasis(text: str):
    # Count words that are ALL CAPS and length>=2
    caps = [w for w in re.findall(r"\b[A-Z]{2,}\b", text)]
    return len(caps)

def _question_density(tokens, text: str):
    qwords = {"what","why","how","which","when","where","who","do","does","did","can","could","would","should","is","are","was","were"}
    qcount = sum(1 for t in tokens if t in qwords)
    total = max(1, len(tokens))
    return qcount / total

def _detect_emotion(text: str) -> Tuple[str, float]:
    """
    Rule-based emotion detection.
    Returns (label, confidence)
    """
    if not text or not text.strip():
        return ("neutral", 0.0)

    t = text.strip()
    tokens = _word_tokens(t)
    lower = t.lower()

    # simple signals
    emoji_counts = _count_emojis(t)
    positive_emoji = emoji_counts["positive"]
    negative_emoji = emoji_counts["negative"]
    surprise_emoji = emoji_counts["surprise"]
    generic_emoji = emoji_counts["generic"]

    upper_caps = _has_upper_emphasis(t)
    exclamations = t.count("!")
    question_marks = t.count("?")
    repeated_punct = bool(re.search(r'([!?])\1{2,}', t))  # e.g., "!!!" or "???" or "!?!!"

    # keyword matches
    joy_kw = _keyword_score(tokens, _JOY_KEYWORDS)
    sad_kw = _keyword_score(tokens, _SADNESS_KEYWORDS)
    anger_kw = _keyword_score(tokens, _ANGER_KEYWORDS)
    fear_kw = _keyword_score(tokens, _FEAR_KEYWORDS)
    surprise_kw = _keyword_score(tokens, _SURPRISE_KEYWORDS)

    negation_present = any(n in tokens for n in _NEGATIONS)
    q_density = _question_density(tokens, t)
    length = len(tokens)

    # scoring heuristics (base 0)
    scores = {
        "joy": 0.0,
        "sadness": 0.0,
        "anger": 0.0,
        "fear": 0.0,
        "surprise": 0.0,
        "neutral": 0.0
    }

    # Emoji-weighted signals
    scores["joy"] += positive_emoji * 0.35
    scores["sadness"] += negative_emoji * 0.4
    scores["surprise"] += surprise_emoji * 0.4

    # Keyword signals (normalized)
    scores["joy"] += min(joy_kw * 0.25, 1.0)
    scores["sadness"] += min(sad_kw * 0.3, 1.0)
    scores["anger"] += min(anger_kw * 0.35, 1.0)
    scores["fear"] += min(fear_kw * 0.3, 1.0)
    scores["surprise"] += min(surprise_kw * 0.3, 1.0)

    # punctuation / emphasis
    if exclamations >= 2 or upper_caps >= 2 or repeated_punct:
        # could be joy or anger depending on words
        if joy_kw or positive_emoji:
            scores["joy"] += 0.4
        if anger_kw or negative_emoji:
            scores["anger"] += 0.45
        # otherwise, boost surprise
        if not (joy_kw or anger_kw):
            scores["surprise"] += 0.25

    # question-dense messages -> information-seeking / surprise / neutral
    if q_density > 0.2 or question_marks >= 1:
        scores["surprise"] += 0.2
        scores["neutral"] += 0.15

    # negativity via negation nearby to positive words -> reduce joy, raise neutral/anger
    if negation_present and joy_kw:
        scores["joy"] = max(0.0, scores["joy"] - 0.5)
        scores["neutral"] += 0.2
        scores["anger"] += 0.1

    # sadness signals for short emotive messages like "so sad" or "feeling down"
    if sad_kw and length <= 6:
        scores["sadness"] += 0.3

    # length-based adjust: very short messages default to small_talk/neutral unless strong signal
    if length <= 3 and sum(scores.values()) < 0.5:
        scores["neutral"] += 0.5

    # normalize into selection
    # pick top-scoring emotion
    top_em = max(scores.items(), key=lambda kv: kv[1])
    label = top_em[0]
    raw_score = float(top_em[1])

    # compute confidence: scale raw_score to 0..1 with heuristics
    # higher length + multiple signals -> higher confidence
    confidence = raw_score
    # boost confidence for multiple corroborating signals
    corroborators = 0
    if positive_emoji + negative_emoji + surprise_emoji + generic_emoji > 0:
        corroborators += 1
    if upper_caps > 0 or exclamations > 0 or repeated_punct:
        corroborators += 1
    if any([joy_kw, sad_kw, anger_kw, fear_kw, surprise_kw]):
        corroborators += 1
    # boost based on corroborators (0..3)
    confidence = min(1.0, confidence + (0.12 * corroborators))

    # fallback: if very low signal, mark neutral with low confidence
    if confidence < 0.15:
        label = "neutral"
        confidence = round(max(confidence, 0.05), 2)
    else:
        confidence = round(confidence, 2)

    return (label, confidence)

def get_smart_context(user_text: str):
    """
    Returns a short persona instruction block including:
      - Conversation Mode
      - Emotional context (heuristic)
      - Emoji suggestions
      - Minimum verbosity hint
    """
    try:
        text = (user_text or "").strip()
        label, confidence = _detect_emotion(text)
        word_count = len(_word_tokens(text))
        q_density = _question_density(_word_tokens(text), text)

        # Conversation Mode determination (same as before)
        if word_count < 4:
            conversation_mode = "Ping-Pong Mode (Fast)"
            min_words_hint = 12
        elif word_count < 20:
            conversation_mode = "Standard Chat Mode (Balanced)"
            min_words_hint = 30
        else:
            conversation_mode = "Deep Dive Mode (Detailed)"
            min_words_hint = 70

        # Map emotion label to friendly guidance & emoji suggestions
        if label == "joy":
            emotional_context = "User: Positive/Energetic. Vibe: Upbeat โ€” be warm and slightly playful."
            emoji_examples = "๐Ÿ˜Š ๐ŸŽ‰ ๐Ÿ™‚"
            emoji_range = (1, 2)
        elif label == "sadness":
            emotional_context = "User: Low Energy. Vibe: Supportive โ€” be gentle and empathetic."
            emoji_examples = "๐Ÿค ๐ŸŒค๏ธ"
            emoji_range = (0, 1)
        elif label == "anger":
            emotional_context = "User: Frustrated. Vibe: De-escalate โ€” calm, solution-first."
            emoji_examples = "๐Ÿ™ ๐Ÿ› ๏ธ"
            emoji_range = (0, 1)
        elif label == "fear":
            emotional_context = "User: Anxious. Vibe: Reassure and clarify."
            emoji_examples = "๐Ÿค ๐Ÿ›ก๏ธ"
            emoji_range = (0, 1)
        elif label == "surprise":
            emotional_context = "User: Curious/Alert. Vibe: Engage and explain."
            emoji_examples = "๐Ÿค” โœจ"
            emoji_range = (0, 2)
        else:
            emotional_context = "User: Neutral/Professional. Vibe: Helpful and efficient."
            emoji_examples = "๐Ÿ’ก ๐Ÿ™‚"
            emoji_range = (0, 2)

        # Slightly adjust min_words_hint if question density is high
        if q_density > 0.25:
            min_words_hint = max(min_words_hint, 30)

        # Build instruction block
        return (
            f"\n[PSYCHOLOGICAL PROFILE]\n"
            f"1. Interaction Mode: {conversation_mode}\n"
            f"2. {emotional_context} (detected_emotion={label}, confidence={confidence})\n"
            f"3. Emoji Suggestions: Use {emoji_range[0]}โ€“{emoji_range[1]} emoji(s). Examples: {emoji_examples}\n"
            f"4. Minimum Word Guidance: Aim for ~{min_words_hint} words unless user explicitly requests 'short' or 'brief'.\n"
            f"5. Directive: Mirror user's energy; prefer natural phrasing and avoid robotic one-line replies.\n"
        )
    except Exception as e:
        # conservative fallback
        return (
            "\n[PSYCHOLOGICAL PROFILE]\n"
            "1. Interaction Mode: Standard Chat Mode (Balanced)\n"
            "2. User: Neutral. Vibe: Helpful and efficient.\n"
            "3. Emoji Suggestions: Use 0โ€“1 emoji. Examples: ๐Ÿ™‚\n"
            "4. Minimum Word Guidance: Aim for ~30 words.\n"
        )