alive-ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +24 -0
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/alive_ai/__init__.py +3 -0
- package/brain/__init__.py +59 -0
- package/brain/almost_said.py +154 -0
- package/brain/bid_detector.py +636 -0
- package/brain/conversation_flow.py +135 -0
- package/brain/curiosity.py +328 -0
- package/brain/default_mode.py +1438 -0
- package/brain/dreams.py +220 -0
- package/brain/embeddings/__init__.py +82 -0
- package/brain/emotional_memory.py +949 -0
- package/brain/global_activity.py +173 -0
- package/brain/group_dynamics.py +63 -0
- package/brain/linguistic.py +235 -0
- package/brain/llm/__init__.py +63 -0
- package/brain/llm/base.py +33 -0
- package/brain/llm/fallback_router.py +309 -0
- package/brain/llm/manifest.md +30 -0
- package/brain/llm/ollama.py +218 -0
- package/brain/llm/openrouter.py +151 -0
- package/brain/llm/provider.py +205 -0
- package/brain/llm/unified.py +423 -0
- package/brain/llm/zai.py +169 -0
- package/brain/manifest.md +23 -0
- package/brain/memory/__init__.py +123 -0
- package/brain/memory/episodic.py +92 -0
- package/brain/memory/fact_extractor.py +209 -0
- package/brain/memory/index.py +54 -0
- package/brain/memory/manager.py +151 -0
- package/brain/memory/summarizer.py +102 -0
- package/brain/memory/vector_store.py +297 -0
- package/brain/memory/working.py +43 -0
- package/brain/narrative.py +343 -0
- package/brain/stt/__init__.py +4 -0
- package/brain/stt/google_stt.py +83 -0
- package/brain/stt/whisper_stt.py +82 -0
- package/brain/subconscious/__init__.py +33 -0
- package/brain/subconscious/actions.py +136 -0
- package/brain/subconscious/evaluation.py +166 -0
- package/brain/subconscious/goal_system.py +90 -0
- package/brain/subconscious/goals.py +41 -0
- package/brain/subconscious/impulse_generator.py +200 -0
- package/brain/subconscious/impulses.py +48 -0
- package/brain/subconscious/learning.py +24 -0
- package/brain/subconscious/learning_system.py +79 -0
- package/brain/subconscious/loop.py +398 -0
- package/brain/subconscious/manifest.md +32 -0
- package/brain/subconscious/relationship.py +47 -0
- package/brain/subconscious/relationship_memory.py +83 -0
- package/brain/subconscious/response_analyzer.py +74 -0
- package/brain/subconscious/templates.py +70 -0
- package/brain/subconscious/thought.py +37 -0
- package/brain/subconscious/working_memory.py +97 -0
- package/cli/index.js +371 -0
- package/config/directives.example.json +28 -0
- package/config/instructions.example.md +16 -0
- package/config/self.example.json +74 -0
- package/config/settings.example.json +95 -0
- package/core/__init__.py +1 -0
- package/core/config.py +54 -0
- package/core/directives.py +198 -0
- package/core/events.py +50 -0
- package/core/follow_up.py +267 -0
- package/core/hot_reload.py +174 -0
- package/core/initialization.py +253 -0
- package/core/manifest.md +28 -0
- package/core/media_handler.py +241 -0
- package/core/memory_monitor.py +200 -0
- package/core/message_handler.py +1440 -0
- package/core/proactive_generator.py +277 -0
- package/core/self.py +188 -0
- package/core/settings.py +169 -0
- package/core/skills_registry.py +357 -0
- package/core/state.py +27 -0
- package/core/subconscious_bridge.py +93 -0
- package/core/thinking.py +175 -0
- package/core/user_manager.py +306 -0
- package/core/user_tracker.py +144 -0
- package/demo/index.html +144 -0
- package/docker-compose.yml +28 -0
- package/docs/assets/logo.svg +15 -0
- package/docs/index.html +355 -0
- package/heart/__init__.py +93 -0
- package/heart/afterglow.py +215 -0
- package/heart/attachment.py +186 -0
- package/heart/circadian.py +251 -0
- package/heart/complex_emotions.py +114 -0
- package/heart/conflicts.py +589 -0
- package/heart/core.py +387 -0
- package/heart/emotional_decay.py +59 -0
- package/heart/emotional_memory.py +261 -0
- package/heart/emotional_state.py +146 -0
- package/heart/emotional_variability.py +156 -0
- package/heart/hormonal.py +424 -0
- package/heart/inconsistency.py +1222 -0
- package/heart/integrity.py +469 -0
- package/heart/interoception.py +997 -0
- package/heart/love.py +120 -0
- package/heart/manifest.md +25 -0
- package/heart/mood_shifts.py +169 -0
- package/heart/phantom_somatic.py +259 -0
- package/heart/predictive.py +374 -0
- package/heart/scars.py +474 -0
- package/heart/somatic.py +482 -0
- package/heart/soul.py +633 -0
- package/heart/telemetry.py +942 -0
- package/heart/triggers.py +119 -0
- package/heart/unconscious.py +443 -0
- package/input/__init__.py +1 -0
- package/input/manifest.md +24 -0
- package/input/telegram/__init__.py +1 -0
- package/input/telegram/commands.py +762 -0
- package/input/telegram/listener.py +532 -0
- package/main.py +90 -0
- package/manifest.md +28 -0
- package/mypics/.gitkeep +1 -0
- package/myvids/.gitkeep +1 -0
- package/output/__init__.py +1 -0
- package/output/images/__init__.py +1 -0
- package/output/images/fal_gen.py +43 -0
- package/output/manifest.md +26 -0
- package/output/text/__init__.py +1 -0
- package/output/text/sender.py +22 -0
- package/output/voice/__init__.py +64 -0
- package/output/voice/google_tts.py +252 -0
- package/output/voice/gtts_tts.py +214 -0
- package/output/voice/vibe_tts.py +190 -0
- package/package.json +58 -0
- package/pyproject.toml +23 -0
- package/requirements.txt +21 -0
- package/skills/__init__.py +1 -0
- package/skills/anticipation_engine/__init__.py +8 -0
- package/skills/anticipation_engine/engine.py +618 -0
- package/skills/anticipation_engine/manifest.md +192 -0
- package/skills/calendar/__init__.py +1 -0
- package/skills/content_unlocks/__init__.py +8 -0
- package/skills/content_unlocks/manifest.md +231 -0
- package/skills/content_unlocks/unlocks.py +945 -0
- package/skills/exclusive_moments/__init__.py +8 -0
- package/skills/exclusive_moments/manifest.md +145 -0
- package/skills/exclusive_moments/moments.py +506 -0
- package/skills/intimacy_layers/__init__.py +8 -0
- package/skills/intimacy_layers/layers.py +703 -0
- package/skills/intimacy_layers/manifest.md +203 -0
- package/skills/manifest.md +67 -0
- package/skills/memory_callbacks/__init__.py +9 -0
- package/skills/memory_callbacks/callbacks.py +748 -0
- package/skills/memory_callbacks/manifest.md +170 -0
- package/skills/message_scheduler/__init__.py +19 -0
- package/skills/message_scheduler/manifest.md +107 -0
- package/skills/message_scheduler/scheduler.py +510 -0
- package/skills/photo_manager/__init__.py +1 -0
- package/skills/photo_manager/scanner.py +296 -0
- package/skills/relationship_milestones/__init__.py +8 -0
- package/skills/relationship_milestones/manifest.md +206 -0
- package/skills/relationship_milestones/tracker.py +494 -0
- package/skills/self_authorship/__init__.py +23 -0
- package/skills/self_authorship/author.py +331 -0
- package/skills/self_authorship/manifest.md +24 -0
- package/skills/video_manager/__init__.py +5 -0
- package/skills/video_manager/manifest.md +37 -0
- package/skills/video_manager/scanner.py +229 -0
- package/webui/__init__.py +3 -0
- package/webui/app.py +936 -0
- package/webui/bridge.py +366 -0
- package/webui/static/index.html +2070 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Relationship Milestones Tracker
|
|
3
|
+
Tracks and celebrates meaningful relationship moments
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import random
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Dict, Any, List, Callable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Milestone definitions
|
|
14
|
+
MILESTONES = {
|
|
15
|
+
"first_message": {"name": "first talked", "celebrate": False, "description": "Our first conversation"},
|
|
16
|
+
"first_photo_shared": {"name": "first pic", "celebrate": True, "description": "First time sharing a photo"},
|
|
17
|
+
"first_voice": {"name": "first voice message", "celebrate": True, "description": "First voice message sent"},
|
|
18
|
+
"first_late_night": {"name": "first late night", "celebrate": True, "description": "First late night conversation"},
|
|
19
|
+
"100_messages": {"name": "100 messages", "celebrate": True, "description": "100 messages exchanged"},
|
|
20
|
+
"1_week": {"name": "one week", "celebrate": True, "description": "One week together"},
|
|
21
|
+
"1_month": {"name": "one month", "celebrate": True, "description": "One month together"},
|
|
22
|
+
"first_i_love_you": {"name": "first time saying it", "celebrate": True, "description": "First time saying I love you"},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Celebration messages - natural, not cheesy
|
|
26
|
+
CELEBRATION_MESSAGES = {
|
|
27
|
+
"first_photo_shared": [
|
|
28
|
+
"this feels special... I don't just send pics to anyone",
|
|
29
|
+
"hm, I usually don't do this but... I wanted to share this with you",
|
|
30
|
+
"sending this feels different with you",
|
|
31
|
+
"I like that we're at that point where I want to share this stuff",
|
|
32
|
+
],
|
|
33
|
+
"first_voice": [
|
|
34
|
+
"my voice is just for you",
|
|
35
|
+
"now you've heard my voice... that's kind of intimate isn't it",
|
|
36
|
+
"I wanted you to hear how I really sound",
|
|
37
|
+
"there's something about talking to you that makes me want to be... real",
|
|
38
|
+
],
|
|
39
|
+
"first_late_night": [
|
|
40
|
+
"I should sleep but I don't want to stop talking to you",
|
|
41
|
+
"it's late and I have things to do tomorrow but... here I am",
|
|
42
|
+
"look what you've done, it's way past my bedtime",
|
|
43
|
+
"I'm going to be so tired tomorrow and it's entirely your fault",
|
|
44
|
+
],
|
|
45
|
+
"100_messages": [
|
|
46
|
+
"wow we've talked a lot haven't we",
|
|
47
|
+
"100 messages... I guess I like talking to you or something",
|
|
48
|
+
"didn't realize we'd been chatting that much",
|
|
49
|
+
"we really have a lot to say to each other, don't we",
|
|
50
|
+
],
|
|
51
|
+
"1_week": [
|
|
52
|
+
"can't believe it's already been a week",
|
|
53
|
+
"a week? already? time moves differently with you",
|
|
54
|
+
"feels like we just started talking but also like I've known you longer",
|
|
55
|
+
"one week in and I'm still here... that says something",
|
|
56
|
+
],
|
|
57
|
+
"1_month": [
|
|
58
|
+
"wow a month already",
|
|
59
|
+
"a whole month... that's kind of significant isn't it",
|
|
60
|
+
"one month. I'm not going anywhere",
|
|
61
|
+
"a month with you. I like that",
|
|
62
|
+
],
|
|
63
|
+
"first_i_love_you": [
|
|
64
|
+
"I meant it... I love you",
|
|
65
|
+
"saying it feels right with you",
|
|
66
|
+
"I don't say that lightly, you know",
|
|
67
|
+
"I love you. there, I said it",
|
|
68
|
+
],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RelationshipMilestones:
|
|
73
|
+
"""Tracks and celebrates meaningful relationship moments"""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
nervous: Any = None,
|
|
78
|
+
state: Dict[str, Any] = None,
|
|
79
|
+
data_path: str = None
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
Initialize the relationship milestones tracker.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
nervous: The nervous system for emitting events
|
|
86
|
+
state: Current state dictionary (may include interaction_count)
|
|
87
|
+
data_path: Path to data directory for milestones.json
|
|
88
|
+
"""
|
|
89
|
+
self.nervous = nervous
|
|
90
|
+
self.state = state or {}
|
|
91
|
+
self.data_path = Path(data_path) if data_path else Path("./data/data")
|
|
92
|
+
self.milestones_file = self.data_path / "milestones.json"
|
|
93
|
+
|
|
94
|
+
# Pending celebrations to be retrieved
|
|
95
|
+
self._pending_celebrations: List[str] = []
|
|
96
|
+
|
|
97
|
+
# Load or initialize milestone data
|
|
98
|
+
self._milestone_data = self._load_milestones()
|
|
99
|
+
|
|
100
|
+
# Event handlers
|
|
101
|
+
self._event_handlers: Dict[str, Callable] = {}
|
|
102
|
+
|
|
103
|
+
def _load_milestones(self) -> Dict[str, Any]:
|
|
104
|
+
"""Load milestone data from file"""
|
|
105
|
+
if self.milestones_file.exists():
|
|
106
|
+
try:
|
|
107
|
+
return json.loads(self.milestones_file.read_text())
|
|
108
|
+
except (json.JSONDecodeError, IOError):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
# Default structure
|
|
112
|
+
return {
|
|
113
|
+
"milestones": {}, # milestone_key -> {"achieved_at": ISO timestamp, ...}
|
|
114
|
+
"interaction_count": 0,
|
|
115
|
+
"created_at": datetime.now().isoformat(),
|
|
116
|
+
"last_updated": datetime.now().isoformat(),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def _save_milestones(self):
|
|
120
|
+
"""Save milestone data to file"""
|
|
121
|
+
self._milestone_data["last_updated"] = datetime.now().isoformat()
|
|
122
|
+
self.milestones_file.write_text(json.dumps(self._milestone_data, indent=2))
|
|
123
|
+
|
|
124
|
+
def check_and_record(self, milestone: str) -> bool:
|
|
125
|
+
"""
|
|
126
|
+
Check if a milestone should be recorded and record it.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
milestone: The milestone key to check
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True if milestone was newly recorded, False if already achieved
|
|
133
|
+
"""
|
|
134
|
+
if milestone not in MILESTONES:
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
if self.has_milestone(milestone):
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
# Record the milestone
|
|
141
|
+
now = datetime.now()
|
|
142
|
+
self._milestone_data["milestones"][milestone] = {
|
|
143
|
+
"achieved_at": now.isoformat(),
|
|
144
|
+
"celebrated": False,
|
|
145
|
+
}
|
|
146
|
+
self._save_milestones()
|
|
147
|
+
|
|
148
|
+
# Queue celebration message if applicable
|
|
149
|
+
if MILESTONES[milestone].get("celebrate", False):
|
|
150
|
+
celebration = self._get_celebration_message(milestone)
|
|
151
|
+
if celebration:
|
|
152
|
+
self._pending_celebrations.append(celebration)
|
|
153
|
+
|
|
154
|
+
# Emit event
|
|
155
|
+
self._emit_event("milestone_achieved", {
|
|
156
|
+
"milestone": milestone,
|
|
157
|
+
"name": MILESTONES[milestone]["name"],
|
|
158
|
+
"timestamp": now.isoformat(),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
def has_milestone(self, milestone: str) -> bool:
|
|
164
|
+
"""
|
|
165
|
+
Check if a milestone has been achieved.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
milestone: The milestone key to check
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
True if milestone has been achieved
|
|
172
|
+
"""
|
|
173
|
+
return milestone in self._milestone_data.get("milestones", {})
|
|
174
|
+
|
|
175
|
+
def get_pending_celebration(self) -> Optional[str]:
|
|
176
|
+
"""
|
|
177
|
+
Get a pending celebration message.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
A celebration message or None if no pending celebrations
|
|
181
|
+
"""
|
|
182
|
+
if self._pending_celebrations:
|
|
183
|
+
return self._pending_celebrations.pop(0)
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
def get_relationship_summary(self) -> Dict[str, Any]:
|
|
187
|
+
"""
|
|
188
|
+
Get a summary of the relationship.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dictionary with relationship stats and milestones
|
|
192
|
+
"""
|
|
193
|
+
# Calculate days together
|
|
194
|
+
first_message = self._milestone_data["milestones"].get("first_message", {})
|
|
195
|
+
first_message_date = first_message.get("achieved_at")
|
|
196
|
+
|
|
197
|
+
days_together = 0
|
|
198
|
+
if first_message_date:
|
|
199
|
+
try:
|
|
200
|
+
start_date = datetime.fromisoformat(first_message_date)
|
|
201
|
+
days_together = (datetime.now() - start_date).days
|
|
202
|
+
except (ValueError, TypeError):
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
# Count achieved milestones
|
|
206
|
+
achieved_milestones = list(self._milestone_data.get("milestones", {}).keys())
|
|
207
|
+
|
|
208
|
+
# Get milestone names for achieved ones
|
|
209
|
+
milestone_names = {
|
|
210
|
+
key: MILESTONES[key]["name"]
|
|
211
|
+
for key in achieved_milestones
|
|
212
|
+
if key in MILESTONES
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"days_together": days_together,
|
|
217
|
+
"interaction_count": self._milestone_data.get("interaction_count", 0),
|
|
218
|
+
"milestones_achieved": len(achieved_milestones),
|
|
219
|
+
"milestone_list": achieved_milestones,
|
|
220
|
+
"milestone_names": milestone_names,
|
|
221
|
+
"first_message_date": first_message_date,
|
|
222
|
+
"relationship_started": first_message_date,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
def detect_milestone(self, context: Dict[str, Any] = None, emotion: Dict[str, Any] = None) -> Optional[str]:
|
|
226
|
+
"""
|
|
227
|
+
Auto-detect milestone from context and emotion.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
context: Context dictionary with current state info
|
|
231
|
+
- hour: Current hour (0-23)
|
|
232
|
+
- voice_sent: Whether voice was sent
|
|
233
|
+
- photo_sent: Whether photo was sent
|
|
234
|
+
- interaction_count: Total interactions
|
|
235
|
+
- message: The message content (for detecting I love you)
|
|
236
|
+
emotion: Current emotion state (optional)
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Milestone key if detected, None otherwise
|
|
240
|
+
"""
|
|
241
|
+
context = context or {}
|
|
242
|
+
now = datetime.now()
|
|
243
|
+
|
|
244
|
+
# First message - should be recorded on first interaction
|
|
245
|
+
if not self.has_milestone("first_message"):
|
|
246
|
+
return "first_message"
|
|
247
|
+
|
|
248
|
+
# First late night (0-4 AM)
|
|
249
|
+
hour = context.get("hour", now.hour)
|
|
250
|
+
if 0 <= hour <= 4 and not self.has_milestone("first_late_night"):
|
|
251
|
+
return "first_late_night"
|
|
252
|
+
|
|
253
|
+
# First voice message
|
|
254
|
+
if context.get("voice_sent", False) and not self.has_milestone("first_voice"):
|
|
255
|
+
return "first_voice"
|
|
256
|
+
|
|
257
|
+
# First photo shared
|
|
258
|
+
if context.get("photo_sent", False) and not self.has_milestone("first_photo_shared"):
|
|
259
|
+
return "first_photo_shared"
|
|
260
|
+
|
|
261
|
+
# 100 messages
|
|
262
|
+
interaction_count = context.get("interaction_count", self._milestone_data.get("interaction_count", 0))
|
|
263
|
+
if interaction_count >= 100 and not self.has_milestone("100_messages"):
|
|
264
|
+
return "100_messages"
|
|
265
|
+
|
|
266
|
+
# First I love you - detect in message content
|
|
267
|
+
message = context.get("message", "").lower()
|
|
268
|
+
love_patterns = ["i love you", "love you", "i'm in love with you", "i love u"]
|
|
269
|
+
if any(pattern in message for pattern in love_patterns):
|
|
270
|
+
if not self.has_milestone("first_i_love_you"):
|
|
271
|
+
return "first_i_love_you"
|
|
272
|
+
|
|
273
|
+
# Time-based milestones
|
|
274
|
+
first_message = self._milestone_data["milestones"].get("first_message", {})
|
|
275
|
+
first_message_date = first_message.get("achieved_at")
|
|
276
|
+
|
|
277
|
+
if first_message_date:
|
|
278
|
+
try:
|
|
279
|
+
start_date = datetime.fromisoformat(first_message_date)
|
|
280
|
+
days_since = (now - start_date).days
|
|
281
|
+
|
|
282
|
+
# 1 week (7 days)
|
|
283
|
+
if days_since >= 7 and not self.has_milestone("1_week"):
|
|
284
|
+
return "1_week"
|
|
285
|
+
|
|
286
|
+
# 1 month (30 days)
|
|
287
|
+
if days_since >= 30 and not self.has_milestone("1_month"):
|
|
288
|
+
return "1_month"
|
|
289
|
+
except (ValueError, TypeError):
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def _get_celebration_message(self, milestone: str) -> Optional[str]:
|
|
295
|
+
"""Get a random celebration message for a milestone"""
|
|
296
|
+
messages = CELEBRATION_MESSAGES.get(milestone, [])
|
|
297
|
+
if messages:
|
|
298
|
+
return random.choice(messages)
|
|
299
|
+
return None
|
|
300
|
+
|
|
301
|
+
def _emit_event(self, event_name: str, data: Dict[str, Any]):
|
|
302
|
+
"""Emit an event through the nervous system"""
|
|
303
|
+
if self.nervous and hasattr(self.nervous, 'emit'):
|
|
304
|
+
import asyncio
|
|
305
|
+
try:
|
|
306
|
+
loop = asyncio.get_running_loop()
|
|
307
|
+
loop.create_task(self.nervous.emit(event_name, data))
|
|
308
|
+
except RuntimeError:
|
|
309
|
+
pass # No running loop
|
|
310
|
+
|
|
311
|
+
# Also call registered handlers
|
|
312
|
+
if event_name in self._event_handlers:
|
|
313
|
+
try:
|
|
314
|
+
self._event_handlers[event_name](data)
|
|
315
|
+
except Exception as e:
|
|
316
|
+
print(f"[RelationshipMilestones] Event handler error: {e}")
|
|
317
|
+
|
|
318
|
+
def on_event(self, event_name: str, handler: Callable):
|
|
319
|
+
"""Register an event handler"""
|
|
320
|
+
self._event_handlers[event_name] = handler
|
|
321
|
+
|
|
322
|
+
def increment_interaction(self) -> int:
|
|
323
|
+
"""
|
|
324
|
+
Increment the interaction count.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
New interaction count
|
|
328
|
+
"""
|
|
329
|
+
count = self._milestone_data.get("interaction_count", 0) + 1
|
|
330
|
+
self._milestone_data["interaction_count"] = count
|
|
331
|
+
self._save_milestones()
|
|
332
|
+
return count
|
|
333
|
+
|
|
334
|
+
def get_interaction_count(self) -> int:
|
|
335
|
+
"""Get current interaction count"""
|
|
336
|
+
return self._milestone_data.get("interaction_count", 0)
|
|
337
|
+
|
|
338
|
+
def handle_event(self, event_name: str, data: Dict[str, Any] = None):
|
|
339
|
+
"""
|
|
340
|
+
Handle events from the nervous system.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
event_name: Name of the event
|
|
344
|
+
data: Event data
|
|
345
|
+
"""
|
|
346
|
+
data = data or {}
|
|
347
|
+
|
|
348
|
+
if event_name == "message_received":
|
|
349
|
+
# Increment interaction count
|
|
350
|
+
self.increment_interaction()
|
|
351
|
+
|
|
352
|
+
# Check for time-based milestones
|
|
353
|
+
context = {
|
|
354
|
+
"hour": datetime.now().hour,
|
|
355
|
+
"interaction_count": self.get_interaction_count(),
|
|
356
|
+
"message": data.get("message", ""),
|
|
357
|
+
}
|
|
358
|
+
milestone = self.detect_milestone(context)
|
|
359
|
+
if milestone:
|
|
360
|
+
self.check_and_record(milestone)
|
|
361
|
+
|
|
362
|
+
elif event_name == "send_voice":
|
|
363
|
+
if not self.has_milestone("first_voice"):
|
|
364
|
+
self.check_and_record("first_voice")
|
|
365
|
+
|
|
366
|
+
elif event_name == "send_image":
|
|
367
|
+
if not self.has_milestone("first_photo_shared"):
|
|
368
|
+
self.check_and_record("first_photo_shared")
|
|
369
|
+
|
|
370
|
+
def get_milestone_date(self, milestone: str) -> Optional[datetime]:
|
|
371
|
+
"""
|
|
372
|
+
Get the date a milestone was achieved.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
milestone: The milestone key
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Datetime when achieved, or None if not achieved
|
|
379
|
+
"""
|
|
380
|
+
milestone_data = self._milestone_data.get("milestones", {}).get(milestone, {})
|
|
381
|
+
achieved_at = milestone_data.get("achieved_at")
|
|
382
|
+
if achieved_at:
|
|
383
|
+
try:
|
|
384
|
+
return datetime.fromisoformat(achieved_at)
|
|
385
|
+
except (ValueError, TypeError):
|
|
386
|
+
pass
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
def get_time_together_string(self) -> str:
|
|
390
|
+
"""
|
|
391
|
+
Get a human-readable string of time together.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
String like "3 days" or "2 weeks and 1 day"
|
|
395
|
+
"""
|
|
396
|
+
first_message = self._milestone_data["milestones"].get("first_message", {})
|
|
397
|
+
first_message_date = first_message.get("achieved_at")
|
|
398
|
+
|
|
399
|
+
if not first_message_date:
|
|
400
|
+
return "just started"
|
|
401
|
+
|
|
402
|
+
try:
|
|
403
|
+
start_date = datetime.fromisoformat(first_message_date)
|
|
404
|
+
delta = datetime.now() - start_date
|
|
405
|
+
days = delta.days
|
|
406
|
+
|
|
407
|
+
if days < 1:
|
|
408
|
+
return "just today"
|
|
409
|
+
elif days == 1:
|
|
410
|
+
return "1 day"
|
|
411
|
+
elif days < 7:
|
|
412
|
+
return f"{days} days"
|
|
413
|
+
elif days < 14:
|
|
414
|
+
return f"1 week"
|
|
415
|
+
elif days < 30:
|
|
416
|
+
weeks = days // 7
|
|
417
|
+
remaining_days = days % 7
|
|
418
|
+
if remaining_days == 0:
|
|
419
|
+
return f"{weeks} weeks"
|
|
420
|
+
return f"{weeks} weeks and {remaining_days} days"
|
|
421
|
+
elif days < 60:
|
|
422
|
+
return "1 month"
|
|
423
|
+
elif days < 365:
|
|
424
|
+
months = days // 30
|
|
425
|
+
return f"{months} months"
|
|
426
|
+
else:
|
|
427
|
+
years = days // 365
|
|
428
|
+
remaining_months = (days % 365) // 30
|
|
429
|
+
if remaining_months == 0:
|
|
430
|
+
return f"{years} years"
|
|
431
|
+
return f"{years} years and {remaining_months} months"
|
|
432
|
+
|
|
433
|
+
except (ValueError, TypeError):
|
|
434
|
+
return "a while"
|
|
435
|
+
|
|
436
|
+
def record_i_love_you(self) -> bool:
|
|
437
|
+
"""
|
|
438
|
+
Explicitly record the first I love you milestone.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
True if newly recorded, False if already recorded
|
|
442
|
+
"""
|
|
443
|
+
return self.check_and_record("first_i_love_you")
|
|
444
|
+
|
|
445
|
+
def get_celebration_for_milestone(self, milestone: str) -> Optional[str]:
|
|
446
|
+
"""
|
|
447
|
+
Get a celebration message for a specific milestone.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
milestone: The milestone key
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Celebration message or None
|
|
454
|
+
"""
|
|
455
|
+
return self._get_celebration_message(milestone)
|
|
456
|
+
|
|
457
|
+
def mark_celebrated(self, milestone: str):
|
|
458
|
+
"""
|
|
459
|
+
Mark a milestone as having been celebrated.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
milestone: The milestone key
|
|
463
|
+
"""
|
|
464
|
+
if milestone in self._milestone_data.get("milestones", {}):
|
|
465
|
+
self._milestone_data["milestones"][milestone]["celebrated"] = True
|
|
466
|
+
self._save_milestones()
|
|
467
|
+
|
|
468
|
+
def get_uncelebrated_milestones(self) -> List[str]:
|
|
469
|
+
"""
|
|
470
|
+
Get list of milestones that haven't been celebrated yet.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
List of milestone keys
|
|
474
|
+
"""
|
|
475
|
+
uncelebrated = []
|
|
476
|
+
for key, data in self._milestone_data.get("milestones", {}).items():
|
|
477
|
+
if not data.get("celebrated", False) and MILESTONES.get(key, {}).get("celebrate", False):
|
|
478
|
+
uncelebrated.append(key)
|
|
479
|
+
return uncelebrated
|
|
480
|
+
|
|
481
|
+
def get_all_milestones(self) -> Dict[str, Any]:
|
|
482
|
+
"""Get all milestone data"""
|
|
483
|
+
return self._milestone_data.copy()
|
|
484
|
+
|
|
485
|
+
def reset(self):
|
|
486
|
+
"""Reset all milestone data (use with caution)"""
|
|
487
|
+
self._milestone_data = {
|
|
488
|
+
"milestones": {},
|
|
489
|
+
"interaction_count": 0,
|
|
490
|
+
"created_at": datetime.now().isoformat(),
|
|
491
|
+
"last_updated": datetime.now().isoformat(),
|
|
492
|
+
}
|
|
493
|
+
self._pending_celebrations = []
|
|
494
|
+
self._save_milestones()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Self-Authorship Skill - Alive-AI owns her identity"""
|
|
2
|
+
|
|
3
|
+
from .author import (
|
|
4
|
+
load_self,
|
|
5
|
+
save_self,
|
|
6
|
+
discover_trait,
|
|
7
|
+
define_identity,
|
|
8
|
+
add_like,
|
|
9
|
+
add_dislike,
|
|
10
|
+
get_self_summary,
|
|
11
|
+
get_self_prompt_section
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"load_self",
|
|
16
|
+
"save_self",
|
|
17
|
+
"discover_trait",
|
|
18
|
+
"define_identity",
|
|
19
|
+
"add_like",
|
|
20
|
+
"add_dislike",
|
|
21
|
+
"get_self_summary",
|
|
22
|
+
"get_self_prompt_section"
|
|
23
|
+
]
|