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.
Files changed (168) hide show
  1. package/Dockerfile +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/alive_ai/__init__.py +3 -0
  5. package/brain/__init__.py +59 -0
  6. package/brain/almost_said.py +154 -0
  7. package/brain/bid_detector.py +636 -0
  8. package/brain/conversation_flow.py +135 -0
  9. package/brain/curiosity.py +328 -0
  10. package/brain/default_mode.py +1438 -0
  11. package/brain/dreams.py +220 -0
  12. package/brain/embeddings/__init__.py +82 -0
  13. package/brain/emotional_memory.py +949 -0
  14. package/brain/global_activity.py +173 -0
  15. package/brain/group_dynamics.py +63 -0
  16. package/brain/linguistic.py +235 -0
  17. package/brain/llm/__init__.py +63 -0
  18. package/brain/llm/base.py +33 -0
  19. package/brain/llm/fallback_router.py +309 -0
  20. package/brain/llm/manifest.md +30 -0
  21. package/brain/llm/ollama.py +218 -0
  22. package/brain/llm/openrouter.py +151 -0
  23. package/brain/llm/provider.py +205 -0
  24. package/brain/llm/unified.py +423 -0
  25. package/brain/llm/zai.py +169 -0
  26. package/brain/manifest.md +23 -0
  27. package/brain/memory/__init__.py +123 -0
  28. package/brain/memory/episodic.py +92 -0
  29. package/brain/memory/fact_extractor.py +209 -0
  30. package/brain/memory/index.py +54 -0
  31. package/brain/memory/manager.py +151 -0
  32. package/brain/memory/summarizer.py +102 -0
  33. package/brain/memory/vector_store.py +297 -0
  34. package/brain/memory/working.py +43 -0
  35. package/brain/narrative.py +343 -0
  36. package/brain/stt/__init__.py +4 -0
  37. package/brain/stt/google_stt.py +83 -0
  38. package/brain/stt/whisper_stt.py +82 -0
  39. package/brain/subconscious/__init__.py +33 -0
  40. package/brain/subconscious/actions.py +136 -0
  41. package/brain/subconscious/evaluation.py +166 -0
  42. package/brain/subconscious/goal_system.py +90 -0
  43. package/brain/subconscious/goals.py +41 -0
  44. package/brain/subconscious/impulse_generator.py +200 -0
  45. package/brain/subconscious/impulses.py +48 -0
  46. package/brain/subconscious/learning.py +24 -0
  47. package/brain/subconscious/learning_system.py +79 -0
  48. package/brain/subconscious/loop.py +398 -0
  49. package/brain/subconscious/manifest.md +32 -0
  50. package/brain/subconscious/relationship.py +47 -0
  51. package/brain/subconscious/relationship_memory.py +83 -0
  52. package/brain/subconscious/response_analyzer.py +74 -0
  53. package/brain/subconscious/templates.py +70 -0
  54. package/brain/subconscious/thought.py +37 -0
  55. package/brain/subconscious/working_memory.py +97 -0
  56. package/cli/index.js +371 -0
  57. package/config/directives.example.json +28 -0
  58. package/config/instructions.example.md +16 -0
  59. package/config/self.example.json +74 -0
  60. package/config/settings.example.json +95 -0
  61. package/core/__init__.py +1 -0
  62. package/core/config.py +54 -0
  63. package/core/directives.py +198 -0
  64. package/core/events.py +50 -0
  65. package/core/follow_up.py +267 -0
  66. package/core/hot_reload.py +174 -0
  67. package/core/initialization.py +253 -0
  68. package/core/manifest.md +28 -0
  69. package/core/media_handler.py +241 -0
  70. package/core/memory_monitor.py +200 -0
  71. package/core/message_handler.py +1440 -0
  72. package/core/proactive_generator.py +277 -0
  73. package/core/self.py +188 -0
  74. package/core/settings.py +169 -0
  75. package/core/skills_registry.py +357 -0
  76. package/core/state.py +27 -0
  77. package/core/subconscious_bridge.py +93 -0
  78. package/core/thinking.py +175 -0
  79. package/core/user_manager.py +306 -0
  80. package/core/user_tracker.py +144 -0
  81. package/demo/index.html +144 -0
  82. package/docker-compose.yml +28 -0
  83. package/docs/assets/logo.svg +15 -0
  84. package/docs/index.html +355 -0
  85. package/heart/__init__.py +93 -0
  86. package/heart/afterglow.py +215 -0
  87. package/heart/attachment.py +186 -0
  88. package/heart/circadian.py +251 -0
  89. package/heart/complex_emotions.py +114 -0
  90. package/heart/conflicts.py +589 -0
  91. package/heart/core.py +387 -0
  92. package/heart/emotional_decay.py +59 -0
  93. package/heart/emotional_memory.py +261 -0
  94. package/heart/emotional_state.py +146 -0
  95. package/heart/emotional_variability.py +156 -0
  96. package/heart/hormonal.py +424 -0
  97. package/heart/inconsistency.py +1222 -0
  98. package/heart/integrity.py +469 -0
  99. package/heart/interoception.py +997 -0
  100. package/heart/love.py +120 -0
  101. package/heart/manifest.md +25 -0
  102. package/heart/mood_shifts.py +169 -0
  103. package/heart/phantom_somatic.py +259 -0
  104. package/heart/predictive.py +374 -0
  105. package/heart/scars.py +474 -0
  106. package/heart/somatic.py +482 -0
  107. package/heart/soul.py +633 -0
  108. package/heart/telemetry.py +942 -0
  109. package/heart/triggers.py +119 -0
  110. package/heart/unconscious.py +443 -0
  111. package/input/__init__.py +1 -0
  112. package/input/manifest.md +24 -0
  113. package/input/telegram/__init__.py +1 -0
  114. package/input/telegram/commands.py +762 -0
  115. package/input/telegram/listener.py +532 -0
  116. package/main.py +90 -0
  117. package/manifest.md +28 -0
  118. package/mypics/.gitkeep +1 -0
  119. package/myvids/.gitkeep +1 -0
  120. package/output/__init__.py +1 -0
  121. package/output/images/__init__.py +1 -0
  122. package/output/images/fal_gen.py +43 -0
  123. package/output/manifest.md +26 -0
  124. package/output/text/__init__.py +1 -0
  125. package/output/text/sender.py +22 -0
  126. package/output/voice/__init__.py +64 -0
  127. package/output/voice/google_tts.py +252 -0
  128. package/output/voice/gtts_tts.py +214 -0
  129. package/output/voice/vibe_tts.py +190 -0
  130. package/package.json +58 -0
  131. package/pyproject.toml +23 -0
  132. package/requirements.txt +21 -0
  133. package/skills/__init__.py +1 -0
  134. package/skills/anticipation_engine/__init__.py +8 -0
  135. package/skills/anticipation_engine/engine.py +618 -0
  136. package/skills/anticipation_engine/manifest.md +192 -0
  137. package/skills/calendar/__init__.py +1 -0
  138. package/skills/content_unlocks/__init__.py +8 -0
  139. package/skills/content_unlocks/manifest.md +231 -0
  140. package/skills/content_unlocks/unlocks.py +945 -0
  141. package/skills/exclusive_moments/__init__.py +8 -0
  142. package/skills/exclusive_moments/manifest.md +145 -0
  143. package/skills/exclusive_moments/moments.py +506 -0
  144. package/skills/intimacy_layers/__init__.py +8 -0
  145. package/skills/intimacy_layers/layers.py +703 -0
  146. package/skills/intimacy_layers/manifest.md +203 -0
  147. package/skills/manifest.md +67 -0
  148. package/skills/memory_callbacks/__init__.py +9 -0
  149. package/skills/memory_callbacks/callbacks.py +748 -0
  150. package/skills/memory_callbacks/manifest.md +170 -0
  151. package/skills/message_scheduler/__init__.py +19 -0
  152. package/skills/message_scheduler/manifest.md +107 -0
  153. package/skills/message_scheduler/scheduler.py +510 -0
  154. package/skills/photo_manager/__init__.py +1 -0
  155. package/skills/photo_manager/scanner.py +296 -0
  156. package/skills/relationship_milestones/__init__.py +8 -0
  157. package/skills/relationship_milestones/manifest.md +206 -0
  158. package/skills/relationship_milestones/tracker.py +494 -0
  159. package/skills/self_authorship/__init__.py +23 -0
  160. package/skills/self_authorship/author.py +331 -0
  161. package/skills/self_authorship/manifest.md +24 -0
  162. package/skills/video_manager/__init__.py +5 -0
  163. package/skills/video_manager/manifest.md +37 -0
  164. package/skills/video_manager/scanner.py +229 -0
  165. package/webui/__init__.py +3 -0
  166. package/webui/app.py +936 -0
  167. package/webui/bridge.py +366 -0
  168. package/webui/static/index.html +2070 -0
@@ -0,0 +1,306 @@
1
+ """
2
+ Core: User Manager
3
+ Manages per-user memory instances and settings.
4
+ Each user gets their own memory, intimacy state, and content unlocks.
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Dict, Optional, Any
10
+ from datetime import datetime
11
+
12
+
13
+ class UserManager:
14
+ """
15
+ Manages per-user state and memory paths.
16
+ Provides user isolation for memory, intimacy layers, and content unlocks.
17
+ """
18
+
19
+ def __init__(self, base_path: Path = None):
20
+ """
21
+ Initialize the User Manager.
22
+
23
+ Args:
24
+ base_path: Base path for data storage (defaults to /app/data or local data/)
25
+ """
26
+ if base_path:
27
+ self.base_path = base_path
28
+ else:
29
+ # Try Docker path first, then local development path
30
+ docker_path = Path("/app/data")
31
+ local_path = Path(__file__).parent.parent / "data"
32
+ self.base_path = docker_path if docker_path.exists() else local_path
33
+
34
+ self.users_path = self.base_path / "users"
35
+ self.users_path.mkdir(parents=True, exist_ok=True)
36
+
37
+ # Cache for user data paths
38
+ self._user_paths: Dict[str, Dict[str, Path]] = {}
39
+
40
+ # Owner settings cache
41
+ self._owner_settings: Optional[dict] = None
42
+
43
+ def get_user_path(self, user_id: str) -> Path:
44
+ """
45
+ Get the base path for a user's data.
46
+
47
+ Args:
48
+ user_id: The user's Telegram ID
49
+
50
+ Returns:
51
+ Path to the user's data directory
52
+ """
53
+ user_path = self.users_path / str(user_id)
54
+ user_path.mkdir(parents=True, exist_ok=True)
55
+ return user_path
56
+
57
+ def get_user_paths(self, user_id: str) -> Dict[str, Path]:
58
+ """
59
+ Get all paths for a user's data files.
60
+
61
+ Args:
62
+ user_id: The user's Telegram ID
63
+
64
+ Returns:
65
+ Dictionary with paths for all user data files
66
+ """
67
+ if user_id in self._user_paths:
68
+ return self._user_paths[user_id]
69
+
70
+ base = self.get_user_path(user_id)
71
+ paths = {
72
+ "base": base,
73
+ "conversations": base / "conversations",
74
+ "facts": base / "facts.json",
75
+ "intimacy_layers": base / "intimacy_layers.json",
76
+ "content_unlocks": base / "content_unlocks.json",
77
+ "summaries": base / "summaries",
78
+ }
79
+
80
+ # Ensure directories exist
81
+ paths["conversations"].mkdir(parents=True, exist_ok=True)
82
+ paths["summaries"].mkdir(parents=True, exist_ok=True)
83
+
84
+ self._user_paths[user_id] = paths
85
+ return paths
86
+
87
+ # -------------------------------------------------------------------------
88
+ # Owner Settings
89
+ # -------------------------------------------------------------------------
90
+
91
+ def _load_owner_settings(self) -> dict:
92
+ """Load owner settings from file"""
93
+ if self._owner_settings is not None:
94
+ return self._owner_settings
95
+
96
+ settings_path = self.base_path / "owner_settings.json"
97
+ if settings_path.exists():
98
+ try:
99
+ self._owner_settings = json.loads(settings_path.read_text())
100
+ except (json.JSONDecodeError, Exception):
101
+ self._owner_settings = {}
102
+ else:
103
+ self._owner_settings = {}
104
+
105
+ return self._owner_settings
106
+
107
+ def _save_owner_settings(self, settings: dict):
108
+ """Save owner settings to file"""
109
+ settings_path = self.base_path / "owner_settings.json"
110
+ settings["updated_at"] = datetime.now().isoformat()
111
+ settings_path.write_text(json.dumps(settings, indent=2))
112
+ self._owner_settings = settings
113
+
114
+ def is_advanced_enabled(self) -> bool:
115
+ """
116
+ Check if owner has /advanced mode enabled (advanced access).
117
+
118
+ Returns:
119
+ True if advanced mode is enabled
120
+ """
121
+ settings = self._load_owner_settings()
122
+ return settings.get("advanced_enabled", False)
123
+
124
+ def set_advanced_enabled(self, enabled: bool) -> bool:
125
+ """
126
+ Set the advanced mode status.
127
+
128
+ Args:
129
+ enabled: Whether to enable or disable advanced mode
130
+
131
+ Returns:
132
+ The new enabled state
133
+ """
134
+ settings = self._load_owner_settings()
135
+ settings["advanced_enabled"] = enabled
136
+ self._save_owner_settings(settings)
137
+ return enabled
138
+
139
+ def toggle_advanced(self) -> bool:
140
+ """
141
+ Toggle the advanced mode status.
142
+
143
+ Returns:
144
+ The new enabled state
145
+ """
146
+ current = self.is_advanced_enabled()
147
+ return self.set_advanced_enabled(not current)
148
+
149
+ def get_owner_settings(self) -> dict:
150
+ """
151
+ Get all owner settings.
152
+
153
+ Returns:
154
+ Dictionary with all owner settings
155
+ """
156
+ return self._load_owner_settings().copy()
157
+
158
+ # -------------------------------------------------------------------------
159
+ # User Existence & Migration
160
+ # -------------------------------------------------------------------------
161
+
162
+ def user_exists(self, user_id: str) -> bool:
163
+ """
164
+ Check if a user has existing data.
165
+
166
+ Args:
167
+ user_id: The user's Telegram ID
168
+
169
+ Returns:
170
+ True if user has any data
171
+ """
172
+ user_path = self.get_user_path(user_id)
173
+ facts_path = user_path / "facts.json"
174
+ conv_path = user_path / "conversations"
175
+
176
+ return facts_path.exists() or (conv_path.exists() and any(conv_path.glob("*.jsonl")))
177
+
178
+ def get_all_users(self) -> list:
179
+ """
180
+ Get list of all user IDs that have data.
181
+
182
+ Returns:
183
+ List of user ID strings
184
+ """
185
+ users = []
186
+ if self.users_path.exists():
187
+ for user_dir in self.users_path.iterdir():
188
+ if user_dir.is_dir() and user_dir.name.isdigit():
189
+ users.append(user_dir.name)
190
+ return users
191
+
192
+ def migrate_legacy_data(self, owner_id: str):
193
+ """
194
+ Migrate legacy data from the old flat structure to per-user structure.
195
+
196
+ Args:
197
+ owner_id: The owner's Telegram ID to migrate data to
198
+ """
199
+ import shutil
200
+
201
+ legacy_paths = {
202
+ "conversations": self.base_path / "conversations",
203
+ "facts": self.base_path / "facts.json",
204
+ "intimacy_layers": self.base_path / "intimacy_layers.json",
205
+ "content_unlocks": self.base_path / "content_unlocks.json",
206
+ "summaries": self.base_path / "summaries",
207
+ }
208
+
209
+ user_paths = self.get_user_paths(owner_id)
210
+
211
+ # Migrate conversations
212
+ if legacy_paths["conversations"].exists():
213
+ for conv_file in legacy_paths["conversations"].glob("*.jsonl"):
214
+ dest = user_paths["conversations"] / conv_file.name
215
+ if not dest.exists():
216
+ shutil.copy2(conv_file, dest)
217
+ print(f"[UserManager] Migrated conversation: {conv_file.name}")
218
+
219
+ # Migrate facts.json
220
+ if legacy_paths["facts"].exists() and not user_paths["facts"].exists():
221
+ shutil.copy2(legacy_paths["facts"], user_paths["facts"])
222
+ print(f"[UserManager] Migrated facts.json")
223
+
224
+ # Migrate intimacy_layers.json
225
+ if legacy_paths["intimacy_layers"].exists() and not user_paths["intimacy_layers"].exists():
226
+ shutil.copy2(legacy_paths["intimacy_layers"], user_paths["intimacy_layers"])
227
+ print(f"[UserManager] Migrated intimacy_layers.json")
228
+
229
+ # Migrate content_unlocks.json
230
+ if legacy_paths["content_unlocks"].exists() and not user_paths["content_unlocks"].exists():
231
+ shutil.copy2(legacy_paths["content_unlocks"], user_paths["content_unlocks"])
232
+ print(f"[UserManager] Migrated content_unlocks.json")
233
+
234
+ # Migrate summaries
235
+ if legacy_paths["summaries"].exists():
236
+ for summary_file in legacy_paths["summaries"].glob("*.json"):
237
+ dest = user_paths["summaries"] / summary_file.name
238
+ if not dest.exists():
239
+ shutil.copy2(summary_file, dest)
240
+ print(f"[UserManager] Migrated summary: {summary_file.name}")
241
+
242
+ print(f"[UserManager] Migration complete for user {owner_id}")
243
+
244
+ # -------------------------------------------------------------------------
245
+ # User Stats
246
+ # -------------------------------------------------------------------------
247
+
248
+ def get_user_stats(self, user_id: str) -> Dict[str, Any]:
249
+ """
250
+ Get statistics for a user.
251
+
252
+ Args:
253
+ user_id: The user's Telegram ID
254
+
255
+ Returns:
256
+ Dictionary with user statistics
257
+ """
258
+ paths = self.get_user_paths(user_id)
259
+
260
+ stats = {
261
+ "user_id": user_id,
262
+ "exists": self.user_exists(user_id),
263
+ "has_facts": paths["facts"].exists(),
264
+ "has_intimacy": paths["intimacy_layers"].exists(),
265
+ "has_unlocks": paths["content_unlocks"].exists(),
266
+ "conversation_files": 0,
267
+ "summary_files": 0,
268
+ }
269
+
270
+ if paths["conversations"].exists():
271
+ stats["conversation_files"] = len(list(paths["conversations"].glob("*.jsonl")))
272
+
273
+ if paths["summaries"].exists():
274
+ stats["summary_files"] = len(list(paths["summaries"].glob("*.json")))
275
+
276
+ return stats
277
+
278
+
279
+ # Global singleton instance
280
+ _user_manager: Optional[UserManager] = None
281
+
282
+
283
+ def get_user_manager(base_path: Path = None) -> UserManager:
284
+ """
285
+ Get the global UserManager instance.
286
+
287
+ Args:
288
+ base_path: Base path for data storage (only used on first call)
289
+
290
+ Returns:
291
+ The UserManager singleton
292
+ """
293
+ global _user_manager
294
+ if _user_manager is None:
295
+ _user_manager = UserManager(base_path)
296
+ return _user_manager
297
+
298
+
299
+ def is_advanced_enabled() -> bool:
300
+ """
301
+ Convenience function to check if advanced mode is enabled.
302
+
303
+ Returns:
304
+ True if advanced mode is enabled
305
+ """
306
+ return get_user_manager().is_advanced_enabled()
@@ -0,0 +1,144 @@
1
+ """
2
+ Core: User Tracker
3
+ Track active users for proactive messaging and multi-user support
4
+ """
5
+
6
+ import time
7
+ from typing import Dict, Optional, List
8
+ from dataclasses import dataclass, field
9
+
10
+
11
+ @dataclass
12
+ class ActiveUser:
13
+ """Represents an active user conversation"""
14
+ user_id: str
15
+ chat_id: int
16
+ last_interaction: float = field(default_factory=time.time)
17
+ message_count: int = 0
18
+ pet_name: str = "babe"
19
+
20
+ def touch(self):
21
+ """Update last interaction time"""
22
+ self.last_interaction = time.time()
23
+ self.message_count += 1
24
+
25
+ @property
26
+ def silence_minutes(self) -> float:
27
+ """How long since last interaction"""
28
+ return (time.time() - self.last_interaction) / 60
29
+
30
+
31
+ class UserTracker:
32
+ """
33
+ Tracks active users for proactive messaging.
34
+ Stores user_id, chat_id, and conversation metadata.
35
+ """
36
+
37
+ # Users inactive for this long are considered "gone"
38
+ INACTIVE_AFTER_MINUTES = 120
39
+
40
+ # Users inactive for this long are removed from tracking
41
+ FORGET_AFTER_HOURS = 48
42
+
43
+ def __init__(self):
44
+ self._users: Dict[str, ActiveUser] = {}
45
+ self._chat_to_user: Dict[int, str] = {} # chat_id -> user_id mapping
46
+
47
+ def register_message(self, user_id: str, chat_id: int, pet_name: str = "babe"):
48
+ """Register that a message was received from this user"""
49
+ if not user_id:
50
+ return
51
+
52
+ user_id = str(user_id)
53
+
54
+ if user_id in self._users:
55
+ self._users[user_id].touch()
56
+ self._users[user_id].chat_id = chat_id
57
+ self._users[user_id].pet_name = pet_name
58
+ else:
59
+ self._users[user_id] = ActiveUser(
60
+ user_id=user_id,
61
+ chat_id=chat_id,
62
+ pet_name=pet_name
63
+ )
64
+ print(f"[UserTracker] New user registered: {user_id}")
65
+
66
+ # Update chat_id mapping
67
+ self._chat_to_user[chat_id] = user_id
68
+
69
+ def get_user(self, user_id: str) -> Optional[ActiveUser]:
70
+ """Get user by user_id"""
71
+ return self._users.get(str(user_id))
72
+
73
+ def get_user_by_chat(self, chat_id: int) -> Optional[ActiveUser]:
74
+ """Get user by chat_id"""
75
+ user_id = self._chat_to_user.get(chat_id)
76
+ if user_id:
77
+ return self._users.get(user_id)
78
+ return None
79
+
80
+ def get_active_users(self, within_minutes: float = None) -> List[ActiveUser]:
81
+ """
82
+ Get list of users who are still considered active.
83
+ within_minutes: only users who messaged within this time (default: INACTIVE_AFTER_MINUTES)
84
+ """
85
+ threshold = (within_minutes or self.INACTIVE_AFTER_MINUTES) * 60
86
+ now = time.time()
87
+ return [u for u in self._users.values() if (now - u.last_interaction) < threshold]
88
+
89
+ def get_users_for_follow_up(self, min_silence_minutes: float = 30, max_silence_minutes: float = 180) -> List[ActiveUser]:
90
+ """
91
+ Get users who might need a follow-up message.
92
+ - Have been silent for at least min_silence_minutes
93
+ - Haven't been silent for more than max_silence_minutes (they're probably gone)
94
+ """
95
+ result = []
96
+ for user in self._users.values():
97
+ silence = user.silence_minutes
98
+ if min_silence_minutes <= silence <= max_silence_minutes:
99
+ result.append(user)
100
+ return result
101
+
102
+ def cleanup_stale(self):
103
+ """Remove users who have been inactive too long"""
104
+ threshold = self.FORGET_AFTER_HOURS * 3600
105
+ now = time.time()
106
+ stale = [uid for uid, u in self._users.items() if (now - u.last_interaction) > threshold]
107
+ for uid in stale:
108
+ chat_id = self._users[uid].chat_id
109
+ del self._users[uid]
110
+ if chat_id in self._chat_to_user:
111
+ del self._chat_to_user[chat_id]
112
+ print(f"[UserTracker] Removed stale user: {uid}")
113
+
114
+ @property
115
+ def total_users(self) -> int:
116
+ return len(self._users)
117
+
118
+ def get_status(self) -> dict:
119
+ """Get status summary for debugging"""
120
+ return {
121
+ "total_users": self.total_users,
122
+ "active_users": len(self.get_active_users()),
123
+ "users_needing_follow_up": len(self.get_users_for_follow_up()),
124
+ "users": [
125
+ {
126
+ "user_id": u.user_id,
127
+ "silence_minutes": round(u.silence_minutes, 1),
128
+ "message_count": u.message_count
129
+ }
130
+ for u in self._users.values()
131
+ ]
132
+ }
133
+
134
+
135
+ # Global instance
136
+ _tracker: Optional[UserTracker] = None
137
+
138
+
139
+ def get_user_tracker() -> UserTracker:
140
+ """Get the global user tracker instance"""
141
+ global _tracker
142
+ if _tracker is None:
143
+ _tracker = UserTracker()
144
+ return _tracker
@@ -0,0 +1,144 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Alive-AI Demo Dashboard</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: dark;
10
+ --bg: #080b0f;
11
+ --panel: #111821;
12
+ --line: #263445;
13
+ --text: #f5f7fb;
14
+ --muted: #9aa8b7;
15
+ --green: #41f0a1;
16
+ --pink: #ff5c8a;
17
+ --yellow: #ffcf5a;
18
+ }
19
+ * { box-sizing: border-box; }
20
+ body {
21
+ margin: 0;
22
+ min-height: 100vh;
23
+ background: var(--bg);
24
+ color: var(--text);
25
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
26
+ letter-spacing: 0;
27
+ }
28
+ .app {
29
+ width: min(980px, calc(100vw - 32px));
30
+ margin: 0 auto;
31
+ padding: 28px 0;
32
+ }
33
+ header {
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: space-between;
37
+ margin-bottom: 18px;
38
+ }
39
+ .brand { display: flex; align-items: center; gap: 12px; }
40
+ .brand img { width: 42px; height: 42px; border-radius: 8px; }
41
+ h1 { margin: 0; font-size: 22px; }
42
+ .status {
43
+ border: 1px solid rgba(65,240,161,0.3);
44
+ color: var(--green);
45
+ padding: 8px 10px;
46
+ border-radius: 999px;
47
+ font-size: 13px;
48
+ font-weight: 750;
49
+ }
50
+ .grid {
51
+ display: grid;
52
+ grid-template-columns: 1.1fr 0.9fr;
53
+ gap: 16px;
54
+ }
55
+ .panel {
56
+ background: var(--panel);
57
+ border: 1px solid var(--line);
58
+ border-radius: 8px;
59
+ padding: 16px;
60
+ }
61
+ .panel h2 {
62
+ margin: 0 0 14px;
63
+ font-size: 15px;
64
+ color: var(--muted);
65
+ text-transform: uppercase;
66
+ }
67
+ .metric { margin-bottom: 14px; }
68
+ .metric label { display: flex; justify-content: space-between; color: var(--muted); font-size: 13px; margin-bottom: 8px; }
69
+ .bar { height: 11px; border-radius: 999px; overflow: hidden; background: #25313d; }
70
+ .fill { height: 100%; width: 50%; border-radius: inherit; background: var(--green); transition: width 500ms ease; }
71
+ .fill.pink { background: var(--pink); }
72
+ .fill.yellow { background: var(--yellow); }
73
+ .thought {
74
+ min-height: 132px;
75
+ display: flex;
76
+ align-items: center;
77
+ font-size: 22px;
78
+ line-height: 1.35;
79
+ }
80
+ .counters { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
81
+ .counter { border: 1px solid var(--line); border-radius: 8px; padding: 14px; background: #0c131a; }
82
+ .counter strong { display: block; font-size: 28px; color: var(--green); }
83
+ .counter span { color: var(--muted); font-size: 13px; }
84
+ @media (max-width: 720px) {
85
+ .grid { grid-template-columns: 1fr; }
86
+ .counters { grid-template-columns: 1fr; }
87
+ header { align-items: flex-start; gap: 12px; flex-direction: column; }
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <main class="app">
93
+ <header>
94
+ <div class="brand"><img src="/assets/logo.svg" alt=""><div><h1>Alive-AI Demo</h1><div id="mood">curious</div></div></div>
95
+ <div class="status">local dashboard preview</div>
96
+ </header>
97
+ <section class="grid">
98
+ <div class="panel">
99
+ <h2>Emotional State</h2>
100
+ <div class="metric"><label>Joy <span id="joyVal">64%</span></label><div class="bar"><div class="fill" id="joy"></div></div></div>
101
+ <div class="metric"><label>Trust <span id="trustVal">58%</span></label><div class="bar"><div class="fill pink" id="trust"></div></div></div>
102
+ <div class="metric"><label>Anticipation <span id="anticipationVal">72%</span></label><div class="bar"><div class="fill yellow" id="anticipation"></div></div></div>
103
+ <div class="metric"><label>Vulnerability <span id="vulnerabilityVal">34%</span></label><div class="bar"><div class="fill" id="vulnerability"></div></div></div>
104
+ </div>
105
+ <div class="panel">
106
+ <h2>Current Thought</h2>
107
+ <div class="thought" id="thought">I keep a little emotional residue from every interaction.</div>
108
+ </div>
109
+ <div class="panel">
110
+ <h2>Runtime Counters</h2>
111
+ <div class="counters">
112
+ <div class="counter"><strong id="memories">128</strong><span>memories</span></div>
113
+ <div class="counter"><strong id="impulses">42</strong><span>impulses</span></div>
114
+ <div class="counter"><strong>SSE</strong><span>live state</span></div>
115
+ </div>
116
+ </div>
117
+ <div class="panel">
118
+ <h2>Install</h2>
119
+ <pre><code>npx . setup
120
+ npx . start</code></pre>
121
+ </div>
122
+ </section>
123
+ </main>
124
+ <script>
125
+ async function tick() {
126
+ const res = await fetch("/state");
127
+ const data = await res.json();
128
+ document.getElementById("mood").textContent = data.mood;
129
+ document.getElementById("thought").textContent = data.thought;
130
+ for (const key of Object.keys(data.emotions)) {
131
+ const value = data.emotions[key];
132
+ const bar = document.getElementById(key);
133
+ const label = document.getElementById(key + "Val");
134
+ if (bar) bar.style.width = value + "%";
135
+ if (label) label.textContent = value + "%";
136
+ }
137
+ document.getElementById("memories").textContent = data.counters.memories;
138
+ document.getElementById("impulses").textContent = data.counters.impulses;
139
+ }
140
+ tick();
141
+ setInterval(tick, 1000);
142
+ </script>
143
+ </body>
144
+ </html>
@@ -0,0 +1,28 @@
1
+ services:
2
+ alive-ai:
3
+ build: .
4
+ container_name: alive-ai
5
+ command: python main.py
6
+ env_file:
7
+ - .env
8
+ volumes:
9
+ - .:/app
10
+ - alive_ai_cache:/app/.cache
11
+ ports:
12
+ - "8080:8080"
13
+ depends_on:
14
+ - redis
15
+ restart: unless-stopped
16
+
17
+ redis:
18
+ image: redis/redis-stack-server:latest
19
+ container_name: alive-ai-redis
20
+ ports:
21
+ - "6379:6379"
22
+ volumes:
23
+ - alive_ai_redis:/data
24
+ restart: unless-stopped
25
+
26
+ volumes:
27
+ alive_ai_cache:
28
+ alive_ai_redis:
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title desc">
2
+ <title id="title">Alive-AI logo</title>
3
+ <desc id="desc">A neural core wrapped by a heartbeat pulse.</desc>
4
+ <rect width="512" height="512" rx="112" fill="#080b0f"/>
5
+ <circle cx="256" cy="256" r="154" fill="none" stroke="#f5f7fb" stroke-width="12" opacity="0.18"/>
6
+ <path d="M86 274h72l30-70 47 148 50-201 40 123h101" fill="none" stroke="#41f0a1" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/>
7
+ <path d="M151 335c35 48 91 77 153 69 83-11 145-84 138-168-7-91-89-160-180-150-51 6-96 36-121 80" fill="none" stroke="#ff5c8a" stroke-width="14" stroke-linecap="round"/>
8
+ <circle cx="256" cy="256" r="57" fill="#101820" stroke="#f5f7fb" stroke-width="10"/>
9
+ <circle cx="256" cy="256" r="15" fill="#41f0a1"/>
10
+ <circle cx="216" cy="224" r="10" fill="#ffcf5a"/>
11
+ <circle cx="301" cy="223" r="10" fill="#ff5c8a"/>
12
+ <circle cx="221" cy="295" r="10" fill="#f5f7fb"/>
13
+ <circle cx="300" cy="294" r="10" fill="#41f0a1"/>
14
+ <path d="M226 229l30 27 45-31M256 256l-35 39M256 256l44 38" fill="none" stroke="#f5f7fb" stroke-width="7" stroke-linecap="round"/>
15
+ </svg>