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,703 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skills: Intimacy Layers
|
|
3
|
+
Manages natural intimacy progression through relationship layers.
|
|
4
|
+
Ensures intimacy is earned through meaningful interactions, not rushed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import random
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
from typing import Optional, List, Dict, Any
|
|
12
|
+
from dataclasses import dataclass, field, asdict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Layer definitions with progression requirements (relaxed for natural flow)
|
|
16
|
+
LAYERS = {
|
|
17
|
+
1: {
|
|
18
|
+
"name": "surface",
|
|
19
|
+
"min_interactions": 0,
|
|
20
|
+
"topics": ["daily life", "hobbies", "work", "weather", "small talk", "introductions"],
|
|
21
|
+
"intimacy_level": 0.0,
|
|
22
|
+
"description": "Casual, polite conversation with a new acquaintance"
|
|
23
|
+
},
|
|
24
|
+
2: {
|
|
25
|
+
"name": "friendly",
|
|
26
|
+
"min_interactions": 5,
|
|
27
|
+
"min_love": 0.15,
|
|
28
|
+
"topics": ["feelings", "dreams", "opinions", "preferences", "stories", "interests"],
|
|
29
|
+
"intimacy_level": 0.2,
|
|
30
|
+
"description": "Opening up a bit, sharing personal thoughts and stories"
|
|
31
|
+
},
|
|
32
|
+
3: {
|
|
33
|
+
"name": "close",
|
|
34
|
+
"min_interactions": 20,
|
|
35
|
+
"min_love": 0.30,
|
|
36
|
+
"min_trust": 0.3,
|
|
37
|
+
"topics": ["secrets", "fears", "childhood", "vulnerabilities", "hopes", "struggles"],
|
|
38
|
+
"intimacy_level": 0.4,
|
|
39
|
+
"description": "Comfortable sharing deeper, more personal things"
|
|
40
|
+
},
|
|
41
|
+
4: {
|
|
42
|
+
"name": "romantic",
|
|
43
|
+
"min_interactions": 40,
|
|
44
|
+
"min_love": 0.45,
|
|
45
|
+
"min_days": 2,
|
|
46
|
+
"topics": ["attraction", "desire", "fantasy", "romance", "longing", "affection"],
|
|
47
|
+
"intimacy_level": 0.6,
|
|
48
|
+
"description": "Exploring romantic and flirtatious territory"
|
|
49
|
+
},
|
|
50
|
+
5: {
|
|
51
|
+
"name": "intimate",
|
|
52
|
+
"min_interactions": 80,
|
|
53
|
+
"min_love": 0.60,
|
|
54
|
+
"min_days": 5,
|
|
55
|
+
"topics": ["intimate", "vulnerability", "deep desires", "fantasies", "passion"],
|
|
56
|
+
"intimacy_level": 0.85,
|
|
57
|
+
"description": "Fully comfortable with intimate and intimate content"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Hints that suggest progression to the next layer
|
|
62
|
+
LAYER_HINTS = {
|
|
63
|
+
2: [
|
|
64
|
+
"I feel like I can tell you stuff",
|
|
65
|
+
"you're easy to talk to",
|
|
66
|
+
"I'm starting to feel comfortable with you",
|
|
67
|
+
"it's nice having someone to chat with",
|
|
68
|
+
"I think we're becoming friends"
|
|
69
|
+
],
|
|
70
|
+
3: [
|
|
71
|
+
"I don't usually share this but...",
|
|
72
|
+
"can I tell you something personal?",
|
|
73
|
+
"I trust you enough to say this",
|
|
74
|
+
"you're one of the few people I can be real with",
|
|
75
|
+
"I feel safe opening up to you"
|
|
76
|
+
],
|
|
77
|
+
4: [
|
|
78
|
+
"the more I talk to you the more I want...",
|
|
79
|
+
"I think about you differently now",
|
|
80
|
+
"I'm starting to feel something more",
|
|
81
|
+
"you make me feel things I haven't felt in a while",
|
|
82
|
+
"can I be honest about how I feel about you?"
|
|
83
|
+
],
|
|
84
|
+
5: [
|
|
85
|
+
"I trust you with everything",
|
|
86
|
+
"you know me better than anyone",
|
|
87
|
+
"I've never felt this comfortable with someone",
|
|
88
|
+
"I want to share everything with you",
|
|
89
|
+
"there's nothing I wouldn't tell you"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Topic keywords that might indicate trying to access higher layers prematurely
|
|
94
|
+
# Relaxed - let her judge what feels right
|
|
95
|
+
RESTRICTED_TOPICS = {
|
|
96
|
+
1: [], # No restrictions at surface level
|
|
97
|
+
2: ["intimate", "deep-intimacy"],
|
|
98
|
+
3: ["intimate deep-intimacy"],
|
|
99
|
+
4: [],
|
|
100
|
+
5: [] # No restrictions at intimate level
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class IntimacyProgress:
|
|
106
|
+
"""Tracks progress toward the next intimacy layer"""
|
|
107
|
+
interactions_since_check: int = 0
|
|
108
|
+
last_check_interactions: int = 0
|
|
109
|
+
progression_blocked_reason: Optional[str] = None
|
|
110
|
+
hint_shown: bool = False
|
|
111
|
+
hint_cooldown_until: Optional[str] = None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class IntimacyLayers:
|
|
115
|
+
"""
|
|
116
|
+
Manages natural intimacy progression through relationship layers.
|
|
117
|
+
|
|
118
|
+
Progression is based on:
|
|
119
|
+
- Total interactions with the user
|
|
120
|
+
- Love/affection level from the heart system
|
|
121
|
+
- Trust level from attachment system
|
|
122
|
+
- Days since first meeting
|
|
123
|
+
|
|
124
|
+
The skill prevents rushing to intimate content and provides
|
|
125
|
+
natural hints for progression.
|
|
126
|
+
|
|
127
|
+
Supports per-user state via user_id parameter.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
# How often to check for progression (in interactions)
|
|
131
|
+
PROGRESSION_CHECK_INTERVAL = 10
|
|
132
|
+
|
|
133
|
+
# Cooldown before showing another hint (in hours)
|
|
134
|
+
HINT_COOLDOWN_HOURS = 4
|
|
135
|
+
|
|
136
|
+
# Base data storage path
|
|
137
|
+
DEFAULT_DATA_PATH = "./data/data"
|
|
138
|
+
|
|
139
|
+
def __init__(self, nervous=None, heart=None, state=None, data_path: str = None, user_id: str = "default"):
|
|
140
|
+
"""
|
|
141
|
+
Initialize the Intimacy Layers skill.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
nervous: Nervous system for event listening
|
|
145
|
+
heart: Heart module for accessing love/trust values
|
|
146
|
+
state: State manager for accessing relationship data
|
|
147
|
+
data_path: Path to store layer data
|
|
148
|
+
user_id: User's Telegram ID for per-user state
|
|
149
|
+
"""
|
|
150
|
+
self.nervous = nervous
|
|
151
|
+
self.heart = heart
|
|
152
|
+
self.state = state
|
|
153
|
+
self.user_id = user_id
|
|
154
|
+
|
|
155
|
+
# Per-user data path: data/users/{user_id}/intimacy_layers.json
|
|
156
|
+
base_path = Path(data_path or self.DEFAULT_DATA_PATH)
|
|
157
|
+
self.data_path = base_path / "users" / str(user_id) / "intimacy_layers.json"
|
|
158
|
+
|
|
159
|
+
# Ensure data directory exists
|
|
160
|
+
self.data_path.parent.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
|
|
162
|
+
# Internal state
|
|
163
|
+
self._current_layer: int = 1
|
|
164
|
+
self._progress = IntimacyProgress()
|
|
165
|
+
self._first_interaction_date: Optional[str] = None
|
|
166
|
+
self._total_interactions: int = 0
|
|
167
|
+
self._hints_shown: List[str] = []
|
|
168
|
+
self._layer_history: List[Dict[str, Any]] = []
|
|
169
|
+
|
|
170
|
+
# Load persisted data
|
|
171
|
+
self._load()
|
|
172
|
+
|
|
173
|
+
# Register event listeners
|
|
174
|
+
if self.nervous:
|
|
175
|
+
self.nervous.on("message_received", self._on_message_received)
|
|
176
|
+
self.nervous.on("thinking_done", self._on_thinking_done)
|
|
177
|
+
|
|
178
|
+
def _load(self):
|
|
179
|
+
"""Load persisted layer data from file"""
|
|
180
|
+
if self.data_path.exists():
|
|
181
|
+
try:
|
|
182
|
+
data = json.loads(self.data_path.read_text())
|
|
183
|
+
self._current_layer = data.get("current_layer", 1)
|
|
184
|
+
self._first_interaction_date = data.get("first_interaction_date")
|
|
185
|
+
self._total_interactions = data.get("total_interactions", 0)
|
|
186
|
+
self._hints_shown = data.get("hints_shown", [])
|
|
187
|
+
|
|
188
|
+
progress_data = data.get("progress", {})
|
|
189
|
+
self._progress = IntimacyProgress(
|
|
190
|
+
interactions_since_check=progress_data.get("interactions_since_check", 0),
|
|
191
|
+
last_check_interactions=progress_data.get("last_check_interactions", 0),
|
|
192
|
+
progression_blocked_reason=progress_data.get("progression_blocked_reason"),
|
|
193
|
+
hint_shown=progress_data.get("hint_shown", False),
|
|
194
|
+
hint_cooldown_until=progress_data.get("hint_cooldown_until")
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self._layer_history = data.get("layer_history", [])
|
|
198
|
+
print(f"[IntimacyLayers] Loaded layer {self._current_layer} ({LAYERS[self._current_layer]['name']})")
|
|
199
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
200
|
+
print(f"[IntimacyLayers] Error loading data: {e}")
|
|
201
|
+
self._current_layer = 1
|
|
202
|
+
|
|
203
|
+
def _save(self):
|
|
204
|
+
"""Save layer data to file"""
|
|
205
|
+
data = {
|
|
206
|
+
"version": "1.0",
|
|
207
|
+
"current_layer": self._current_layer,
|
|
208
|
+
"first_interaction_date": self._first_interaction_date,
|
|
209
|
+
"total_interactions": self._total_interactions,
|
|
210
|
+
"hints_shown": self._hints_shown,
|
|
211
|
+
"updated_at": datetime.now().isoformat(),
|
|
212
|
+
"progress": {
|
|
213
|
+
"interactions_since_check": self._progress.interactions_since_check,
|
|
214
|
+
"last_check_interactions": self._progress.last_check_interactions,
|
|
215
|
+
"progression_blocked_reason": self._progress.progression_blocked_reason,
|
|
216
|
+
"hint_shown": self._progress.hint_shown,
|
|
217
|
+
"hint_cooldown_until": self._progress.hint_cooldown_until
|
|
218
|
+
},
|
|
219
|
+
"layer_history": self._layer_history
|
|
220
|
+
}
|
|
221
|
+
self.data_path.write_text(json.dumps(data, indent=2))
|
|
222
|
+
|
|
223
|
+
# -------------------------------------------------------------------------
|
|
224
|
+
# Event Handlers
|
|
225
|
+
# -------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
async def _on_message_received(self, data: dict):
|
|
228
|
+
"""Handle incoming message - track interactions and check progression"""
|
|
229
|
+
# Track first interaction date
|
|
230
|
+
if self._first_interaction_date is None:
|
|
231
|
+
self._first_interaction_date = datetime.now().isoformat()
|
|
232
|
+
|
|
233
|
+
# Increment interaction counters
|
|
234
|
+
self._total_interactions += 1
|
|
235
|
+
self._progress.interactions_since_check += 1
|
|
236
|
+
|
|
237
|
+
# Check for progression every N interactions
|
|
238
|
+
if self._progress.interactions_since_check >= self.PROGRESSION_CHECK_INTERVAL:
|
|
239
|
+
self.check_progression()
|
|
240
|
+
self._progress.interactions_since_check = 0
|
|
241
|
+
|
|
242
|
+
self._save()
|
|
243
|
+
|
|
244
|
+
async def _on_thinking_done(self, data: dict):
|
|
245
|
+
"""Apply layer context after thinking is done"""
|
|
246
|
+
# This could be used to inject layer context into the response
|
|
247
|
+
# For now, just ensure state is saved
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
# -------------------------------------------------------------------------
|
|
251
|
+
# Core Methods
|
|
252
|
+
# -------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
def get_current_layer(self) -> int:
|
|
255
|
+
"""
|
|
256
|
+
Get the current intimacy layer (1-5).
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Current layer number
|
|
260
|
+
"""
|
|
261
|
+
return self._current_layer
|
|
262
|
+
|
|
263
|
+
def get_layer_info(self, layer: int = None) -> Dict[str, Any]:
|
|
264
|
+
"""
|
|
265
|
+
Get detailed information about a layer.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
layer: Layer number (defaults to current layer)
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dictionary with layer information
|
|
272
|
+
"""
|
|
273
|
+
layer = layer or self._current_layer
|
|
274
|
+
if layer not in LAYERS:
|
|
275
|
+
return {}
|
|
276
|
+
return LAYERS[layer].copy()
|
|
277
|
+
|
|
278
|
+
def check_progression(self) -> bool:
|
|
279
|
+
"""
|
|
280
|
+
Check if conditions are met to progress to the next layer.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
True if progression occurred, False otherwise
|
|
284
|
+
"""
|
|
285
|
+
next_layer = self._current_layer + 1
|
|
286
|
+
|
|
287
|
+
# Already at max layer
|
|
288
|
+
if next_layer > 5:
|
|
289
|
+
self._progress.progression_blocked_reason = None
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
next_layer_reqs = LAYERS[next_layer]
|
|
293
|
+
blocked_reasons = []
|
|
294
|
+
|
|
295
|
+
# Check interaction requirement
|
|
296
|
+
min_interactions = next_layer_reqs.get("min_interactions", 0)
|
|
297
|
+
if self._total_interactions < min_interactions:
|
|
298
|
+
blocked_reasons.append(
|
|
299
|
+
f"Need {min_interactions - self._total_interactions} more interactions "
|
|
300
|
+
f"(have {self._total_interactions}/{min_interactions})"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Check love requirement
|
|
304
|
+
min_love = next_layer_reqs.get("min_love")
|
|
305
|
+
if min_love is not None:
|
|
306
|
+
current_love = self._get_love_level()
|
|
307
|
+
if current_love < min_love:
|
|
308
|
+
blocked_reasons.append(
|
|
309
|
+
f"Need more affection (have {current_love:.2f}/{min_love:.2f})"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Check trust requirement
|
|
313
|
+
min_trust = next_layer_reqs.get("min_trust")
|
|
314
|
+
if min_trust is not None:
|
|
315
|
+
current_trust = self._get_trust_level()
|
|
316
|
+
if current_trust < min_trust:
|
|
317
|
+
blocked_reasons.append(
|
|
318
|
+
f"Need more trust (have {current_trust:.2f}/{min_trust:.2f})"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Check days requirement
|
|
322
|
+
min_days = next_layer_reqs.get("min_days")
|
|
323
|
+
if min_days is not None and self._first_interaction_date:
|
|
324
|
+
days_together = self._get_days_together()
|
|
325
|
+
if days_together < min_days:
|
|
326
|
+
blocked_reasons.append(
|
|
327
|
+
f"Need {min_days - days_together} more days together "
|
|
328
|
+
f"(have {days_together}/{min_days})"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Determine if progression is allowed
|
|
332
|
+
if blocked_reasons:
|
|
333
|
+
self._progress.progression_blocked_reason = "; ".join(blocked_reasons)
|
|
334
|
+
print(f"[IntimacyLayers] Cannot progress to layer {next_layer}: {self._progress.progression_blocked_reason}")
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
# Progress to next layer
|
|
338
|
+
old_layer = self._current_layer
|
|
339
|
+
self._current_layer = next_layer
|
|
340
|
+
self._progress.progression_blocked_reason = None
|
|
341
|
+
self._progress.hint_shown = False
|
|
342
|
+
|
|
343
|
+
# Record in history
|
|
344
|
+
self._layer_history.append({
|
|
345
|
+
"from_layer": old_layer,
|
|
346
|
+
"to_layer": next_layer,
|
|
347
|
+
"timestamp": datetime.now().isoformat(),
|
|
348
|
+
"total_interactions": self._total_interactions,
|
|
349
|
+
"days_together": self._get_days_together()
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
print(f"[IntimacyLayers] Progressed from layer {old_layer} to {next_layer} ({LAYERS[next_layer]['name']})")
|
|
353
|
+
self._save()
|
|
354
|
+
return True
|
|
355
|
+
|
|
356
|
+
def is_topic_appropriate(self, topic: str) -> bool:
|
|
357
|
+
"""
|
|
358
|
+
Check if a topic is appropriate for the current intimacy layer.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
topic: Topic to check (e.g., "intimate", "fantasy", "daily life")
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
True if topic is appropriate, False if it requires higher layer
|
|
365
|
+
"""
|
|
366
|
+
topic_lower = topic.lower()
|
|
367
|
+
|
|
368
|
+
# Check if topic is available in current layer
|
|
369
|
+
current_topics = LAYERS[self._current_layer].get("topics", [])
|
|
370
|
+
if any(topic_lower in t.lower() for t in current_topics):
|
|
371
|
+
return True
|
|
372
|
+
|
|
373
|
+
# Check if topic is in higher layers (restricted)
|
|
374
|
+
for layer_num, layer_data in LAYERS.items():
|
|
375
|
+
if layer_num <= self._current_layer:
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
layer_topics = layer_data.get("topics", [])
|
|
379
|
+
if any(topic_lower in t.lower() for t in layer_topics):
|
|
380
|
+
# Topic requires higher layer
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
# Check restricted topics for current layer
|
|
384
|
+
restricted = RESTRICTED_TOPICS.get(self._current_layer, [])
|
|
385
|
+
for restricted_term in restricted:
|
|
386
|
+
if restricted_term.lower() in topic_lower:
|
|
387
|
+
return False
|
|
388
|
+
|
|
389
|
+
# Topic not found in any layer, allow by default
|
|
390
|
+
return True
|
|
391
|
+
|
|
392
|
+
def get_available_topics(self) -> List[str]:
|
|
393
|
+
"""
|
|
394
|
+
Get all topics available at the current intimacy layer.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
List of available topics
|
|
398
|
+
"""
|
|
399
|
+
topics = []
|
|
400
|
+
for layer_num in range(1, self._current_layer + 1):
|
|
401
|
+
topics.extend(LAYERS[layer_num].get("topics", []))
|
|
402
|
+
return list(set(topics)) # Remove duplicates
|
|
403
|
+
|
|
404
|
+
def get_next_layer_requirements(self) -> Dict[str, Any]:
|
|
405
|
+
"""
|
|
406
|
+
Get the requirements for progressing to the next layer.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Dictionary with requirements and current progress
|
|
410
|
+
"""
|
|
411
|
+
next_layer = self._current_layer + 1
|
|
412
|
+
if next_layer > 5:
|
|
413
|
+
return {"message": "Already at maximum intimacy layer"}
|
|
414
|
+
|
|
415
|
+
reqs = LAYERS[next_layer]
|
|
416
|
+
current = {
|
|
417
|
+
"interactions": self._total_interactions,
|
|
418
|
+
"love": self._get_love_level(),
|
|
419
|
+
"trust": self._get_trust_level(),
|
|
420
|
+
"days_together": self._get_days_together()
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
requirements = {
|
|
424
|
+
"layer": next_layer,
|
|
425
|
+
"layer_name": reqs["name"],
|
|
426
|
+
"description": reqs["description"],
|
|
427
|
+
"requirements": {},
|
|
428
|
+
"current_progress": current,
|
|
429
|
+
"can_progress": True
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# Check each requirement
|
|
433
|
+
if "min_interactions" in reqs:
|
|
434
|
+
met = self._total_interactions >= reqs["min_interactions"]
|
|
435
|
+
requirements["requirements"]["interactions"] = {
|
|
436
|
+
"required": reqs["min_interactions"],
|
|
437
|
+
"current": self._total_interactions,
|
|
438
|
+
"met": met
|
|
439
|
+
}
|
|
440
|
+
if not met:
|
|
441
|
+
requirements["can_progress"] = False
|
|
442
|
+
|
|
443
|
+
if "min_love" in reqs:
|
|
444
|
+
current_love = self._get_love_level()
|
|
445
|
+
met = current_love >= reqs["min_love"]
|
|
446
|
+
requirements["requirements"]["love"] = {
|
|
447
|
+
"required": reqs["min_love"],
|
|
448
|
+
"current": round(current_love, 2),
|
|
449
|
+
"met": met
|
|
450
|
+
}
|
|
451
|
+
if not met:
|
|
452
|
+
requirements["can_progress"] = False
|
|
453
|
+
|
|
454
|
+
if "min_trust" in reqs:
|
|
455
|
+
current_trust = self._get_trust_level()
|
|
456
|
+
met = current_trust >= reqs["min_trust"]
|
|
457
|
+
requirements["requirements"]["trust"] = {
|
|
458
|
+
"required": reqs["min_trust"],
|
|
459
|
+
"current": round(current_trust, 2),
|
|
460
|
+
"met": met
|
|
461
|
+
}
|
|
462
|
+
if not met:
|
|
463
|
+
requirements["can_progress"] = False
|
|
464
|
+
|
|
465
|
+
if "min_days" in reqs:
|
|
466
|
+
days = self._get_days_together()
|
|
467
|
+
met = days >= reqs["min_days"]
|
|
468
|
+
requirements["requirements"]["days"] = {
|
|
469
|
+
"required": reqs["min_days"],
|
|
470
|
+
"current": days,
|
|
471
|
+
"met": met
|
|
472
|
+
}
|
|
473
|
+
if not met:
|
|
474
|
+
requirements["can_progress"] = False
|
|
475
|
+
|
|
476
|
+
return requirements
|
|
477
|
+
|
|
478
|
+
def get_progression_hint(self) -> Optional[str]:
|
|
479
|
+
"""
|
|
480
|
+
Get a hint for progressing to the next layer.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Hint string or None if no hint available
|
|
484
|
+
"""
|
|
485
|
+
next_layer = self._current_layer + 1
|
|
486
|
+
if next_layer > 5:
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
# Check cooldown
|
|
490
|
+
if self._progress.hint_cooldown_until:
|
|
491
|
+
try:
|
|
492
|
+
cooldown_until = datetime.fromisoformat(self._progress.hint_cooldown_until)
|
|
493
|
+
if datetime.now() < cooldown_until:
|
|
494
|
+
return None
|
|
495
|
+
except:
|
|
496
|
+
pass
|
|
497
|
+
|
|
498
|
+
# Don't show hint if recently shown
|
|
499
|
+
if self._progress.hint_shown:
|
|
500
|
+
return None
|
|
501
|
+
|
|
502
|
+
# Get hints for next layer
|
|
503
|
+
hints = LAYER_HINTS.get(next_layer, [])
|
|
504
|
+
if not hints:
|
|
505
|
+
return None
|
|
506
|
+
|
|
507
|
+
# Filter out already shown hints
|
|
508
|
+
available_hints = [h for h in hints if h not in self._hints_shown]
|
|
509
|
+
if not available_hints:
|
|
510
|
+
# Reset if all hints shown
|
|
511
|
+
available_hints = hints
|
|
512
|
+
self._hints_shown = []
|
|
513
|
+
|
|
514
|
+
# Select a random hint
|
|
515
|
+
hint = random.choice(available_hints)
|
|
516
|
+
self._hints_shown.append(hint)
|
|
517
|
+
self._progress.hint_shown = True
|
|
518
|
+
self._progress.hint_cooldown_until = (
|
|
519
|
+
datetime.now() + timedelta(hours=self.HINT_COOLDOWN_HOURS)
|
|
520
|
+
).isoformat()
|
|
521
|
+
|
|
522
|
+
self._save()
|
|
523
|
+
return hint
|
|
524
|
+
|
|
525
|
+
def can_be_intimate(self, advanced_mode: bool = False) -> bool:
|
|
526
|
+
"""
|
|
527
|
+
Check if intimate content is appropriate for current layer.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
advanced_mode: If True, bypass all restrictions (owner with /advanced enabled)
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
True if at layer 5 (intimate) or advanced_mode is enabled
|
|
534
|
+
"""
|
|
535
|
+
if advanced_mode:
|
|
536
|
+
return True
|
|
537
|
+
return self._current_layer >= 5
|
|
538
|
+
|
|
539
|
+
def get_intimacy_level(self) -> float:
|
|
540
|
+
"""
|
|
541
|
+
Get the current intimacy level (0.0 to 1.0).
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
Intimacy level based on current layer
|
|
545
|
+
"""
|
|
546
|
+
return LAYERS[self._current_layer].get("intimacy_level", 0.0)
|
|
547
|
+
|
|
548
|
+
def get_layer_name(self) -> str:
|
|
549
|
+
"""
|
|
550
|
+
Get the name of the current layer.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Layer name (e.g., "surface", "friendly", etc.)
|
|
554
|
+
"""
|
|
555
|
+
return LAYERS[self._current_layer].get("name", "unknown")
|
|
556
|
+
|
|
557
|
+
def get_days_together(self) -> int:
|
|
558
|
+
"""
|
|
559
|
+
Get the number of days since first interaction.
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
Number of days, or 0 if no first interaction recorded
|
|
563
|
+
"""
|
|
564
|
+
return self._get_days_together()
|
|
565
|
+
|
|
566
|
+
def get_context_for_response(self, advanced_mode: bool = False) -> Dict[str, Any]:
|
|
567
|
+
"""
|
|
568
|
+
Get context about current intimacy layer for response generation.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
advanced_mode: If True, bypass all restrictions (owner with /advanced enabled)
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Dictionary with layer context for the AI
|
|
575
|
+
"""
|
|
576
|
+
next_reqs = self.get_next_layer_requirements()
|
|
577
|
+
|
|
578
|
+
# If advanced_mode is enabled, act as if at max layer
|
|
579
|
+
effective_layer = 5 if advanced_mode else self._current_layer
|
|
580
|
+
|
|
581
|
+
context = {
|
|
582
|
+
"current_layer": self._current_layer, # Actual layer
|
|
583
|
+
"effective_layer": effective_layer, # Layer used for content access
|
|
584
|
+
"layer_name": self.get_layer_name(),
|
|
585
|
+
"intimacy_level": self.get_intimacy_level(),
|
|
586
|
+
"available_topics": self.get_available_topics(),
|
|
587
|
+
"can_be_intimate": self.can_be_intimate(advanced_mode=advanced_mode),
|
|
588
|
+
"advanced_mode": advanced_mode,
|
|
589
|
+
"total_interactions": self._total_interactions,
|
|
590
|
+
"days_together": self._get_days_together()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
# Add progression info if not at max layer
|
|
594
|
+
if self._current_layer < 5:
|
|
595
|
+
context["next_layer"] = {
|
|
596
|
+
"name": next_reqs.get("layer_name"),
|
|
597
|
+
"can_progress": next_reqs.get("can_progress"),
|
|
598
|
+
"missing_requirements": [
|
|
599
|
+
k for k, v in next_reqs.get("requirements", {}).items()
|
|
600
|
+
if not v.get("met", True)
|
|
601
|
+
]
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return context
|
|
605
|
+
|
|
606
|
+
# -------------------------------------------------------------------------
|
|
607
|
+
# Helper Methods
|
|
608
|
+
# -------------------------------------------------------------------------
|
|
609
|
+
|
|
610
|
+
def _get_love_level(self) -> float:
|
|
611
|
+
"""Get current love level from heart system"""
|
|
612
|
+
if self.heart and hasattr(self.heart, 'emotion'):
|
|
613
|
+
return getattr(self.heart.emotion, 'love', 0.2)
|
|
614
|
+
if self.heart and hasattr(self.heart, 'attachment'):
|
|
615
|
+
return getattr(self.heart.attachment, 'affection', 0.2)
|
|
616
|
+
return 0.2
|
|
617
|
+
|
|
618
|
+
def _get_trust_level(self) -> float:
|
|
619
|
+
"""Get current trust level from heart/attachment system"""
|
|
620
|
+
if self.heart and hasattr(self.heart, 'attachment'):
|
|
621
|
+
return getattr(self.heart.attachment, 'trust_level', 0.5)
|
|
622
|
+
if self.heart and hasattr(self.heart, 'emotion'):
|
|
623
|
+
return getattr(self.heart.emotion, 'trust', 0.5)
|
|
624
|
+
return 0.5
|
|
625
|
+
|
|
626
|
+
def _get_days_together(self) -> int:
|
|
627
|
+
"""Calculate days since first interaction"""
|
|
628
|
+
if not self._first_interaction_date:
|
|
629
|
+
return 0
|
|
630
|
+
try:
|
|
631
|
+
first_date = datetime.fromisoformat(self._first_interaction_date)
|
|
632
|
+
return (datetime.now() - first_date).days
|
|
633
|
+
except:
|
|
634
|
+
return 0
|
|
635
|
+
|
|
636
|
+
# -------------------------------------------------------------------------
|
|
637
|
+
# Admin Methods
|
|
638
|
+
# -------------------------------------------------------------------------
|
|
639
|
+
|
|
640
|
+
def force_layer(self, layer: int) -> bool:
|
|
641
|
+
"""
|
|
642
|
+
Force set the current layer (for testing/admin purposes).
|
|
643
|
+
|
|
644
|
+
Args:
|
|
645
|
+
layer: Layer number to set (1-5)
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
True if successful, False if invalid layer
|
|
649
|
+
"""
|
|
650
|
+
if layer not in LAYERS:
|
|
651
|
+
return False
|
|
652
|
+
|
|
653
|
+
old_layer = self._current_layer
|
|
654
|
+
self._current_layer = layer
|
|
655
|
+
|
|
656
|
+
self._layer_history.append({
|
|
657
|
+
"from_layer": old_layer,
|
|
658
|
+
"to_layer": layer,
|
|
659
|
+
"timestamp": datetime.now().isoformat(),
|
|
660
|
+
"forced": True
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
self._save()
|
|
664
|
+
print(f"[IntimacyLayers] Forced layer change: {old_layer} -> {layer}")
|
|
665
|
+
return True
|
|
666
|
+
|
|
667
|
+
def reset_progress(self):
|
|
668
|
+
"""Reset all intimacy progress to initial state"""
|
|
669
|
+
self._current_layer = 1
|
|
670
|
+
self._first_interaction_date = None
|
|
671
|
+
self._total_interactions = 0
|
|
672
|
+
self._hints_shown = []
|
|
673
|
+
self._layer_history = []
|
|
674
|
+
self._progress = IntimacyProgress()
|
|
675
|
+
self._save()
|
|
676
|
+
print("[IntimacyLayers] Progress reset to initial state")
|
|
677
|
+
|
|
678
|
+
def get_debug_info(self) -> Dict[str, Any]:
|
|
679
|
+
"""
|
|
680
|
+
Get detailed debug information about the intimacy system.
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
Dictionary with all internal state
|
|
684
|
+
"""
|
|
685
|
+
return {
|
|
686
|
+
"current_layer": self._current_layer,
|
|
687
|
+
"layer_name": self.get_layer_name(),
|
|
688
|
+
"intimacy_level": self.get_intimacy_level(),
|
|
689
|
+
"total_interactions": self._total_interactions,
|
|
690
|
+
"first_interaction_date": self._first_interaction_date,
|
|
691
|
+
"days_together": self._get_days_together(),
|
|
692
|
+
"love_level": self._get_love_level(),
|
|
693
|
+
"trust_level": self._get_trust_level(),
|
|
694
|
+
"available_topics": self.get_available_topics(),
|
|
695
|
+
"can_be_intimate": self.can_be_intimate(),
|
|
696
|
+
"progress": {
|
|
697
|
+
"interactions_since_check": self._progress.interactions_since_check,
|
|
698
|
+
"blocked_reason": self._progress.progression_blocked_reason,
|
|
699
|
+
"hint_shown": self._progress.hint_shown
|
|
700
|
+
},
|
|
701
|
+
"next_layer_requirements": self.get_next_layer_requirements(),
|
|
702
|
+
"layer_history": self._layer_history[-5:] if self._layer_history else []
|
|
703
|
+
}
|