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,762 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telegram Commands
|
|
3
|
+
Admin commands to manage Alive-AI
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommandHandler:
|
|
14
|
+
"""Handle admin commands for Alive-AI management"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, nervous, heart, subconscious, llm, voice, photos, videos):
|
|
17
|
+
self.nervous = nervous
|
|
18
|
+
self.heart = heart
|
|
19
|
+
self.subconscious = subconscious
|
|
20
|
+
self.llm = llm
|
|
21
|
+
self.voice = voice
|
|
22
|
+
self.photos = photos
|
|
23
|
+
self.videos = videos
|
|
24
|
+
|
|
25
|
+
# Owner commands handler (initialized later with ai reference)
|
|
26
|
+
self._owner_commands = None
|
|
27
|
+
|
|
28
|
+
def init_owner_commands(self, ai):
|
|
29
|
+
"""Initialize owner commands with ai reference"""
|
|
30
|
+
self._owner_commands = OwnerCommands(ai, self)
|
|
31
|
+
|
|
32
|
+
async def handle(self, update: Update, command: str, args: list):
|
|
33
|
+
"""Route command to handler"""
|
|
34
|
+
# First check if it's an owner command
|
|
35
|
+
if self._owner_commands and self._owner_commands.is_owner_command(command):
|
|
36
|
+
await self._owner_commands.handle(update, command, args)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
# Standard commands (public)
|
|
40
|
+
handlers = {
|
|
41
|
+
"start": self._cmd_start,
|
|
42
|
+
"help": self._cmd_help,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
handler = handlers.get(command.lower())
|
|
46
|
+
if handler:
|
|
47
|
+
await handler(update, args)
|
|
48
|
+
else:
|
|
49
|
+
await update.message.reply_text(f"Unknown command: /{command}")
|
|
50
|
+
|
|
51
|
+
async def _cmd_start(self, update: Update, args: list):
|
|
52
|
+
"""Welcome message"""
|
|
53
|
+
msg = "Hey! I'm Alive-AI\n\nUse /help to see what I can do."
|
|
54
|
+
|
|
55
|
+
# Add owner hint if owner
|
|
56
|
+
if self._owner_commands and self._owner_commands.is_owner(update):
|
|
57
|
+
msg += "\n\n/owner - Owner commands menu"
|
|
58
|
+
|
|
59
|
+
await update.message.reply_text(msg)
|
|
60
|
+
|
|
61
|
+
async def _cmd_help(self, update: Update, args: list):
|
|
62
|
+
"""Show all commands"""
|
|
63
|
+
msg = (
|
|
64
|
+
"Alive-AI Commands\n\n"
|
|
65
|
+
"/start - Welcome message\n"
|
|
66
|
+
"/help - This message"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Add owner commands hint if owner
|
|
70
|
+
if self._owner_commands and self._owner_commands.is_owner(update):
|
|
71
|
+
msg += "\n\n/owner - Owner commands menu (admin)"
|
|
72
|
+
|
|
73
|
+
await update.message.reply_text(msg, parse_mode="Markdown")
|
|
74
|
+
|
|
75
|
+
async def _cmd_reset_memories(self, update: Update, args: list):
|
|
76
|
+
"""Delete ALL memories and start fresh"""
|
|
77
|
+
import redis
|
|
78
|
+
import shutil
|
|
79
|
+
|
|
80
|
+
await update.message.reply_text("Wiping all memories...")
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
# Clear Redis vector store
|
|
84
|
+
r = redis.Redis(host='redis', port=6379, decode_responses=False)
|
|
85
|
+
keys = r.keys("*")
|
|
86
|
+
if keys:
|
|
87
|
+
r.delete(*keys)
|
|
88
|
+
print(f"[Memory] Deleted {len(keys)} keys from Redis")
|
|
89
|
+
r.close()
|
|
90
|
+
|
|
91
|
+
# Clear ALL data directories
|
|
92
|
+
data_base = Path(os.environ.get("DATA_PATH", "/app/data"))
|
|
93
|
+
dirs_to_clear = ["conversations", "summaries"]
|
|
94
|
+
|
|
95
|
+
for dir_name in dirs_to_clear:
|
|
96
|
+
dir_path = data_base / dir_name
|
|
97
|
+
if dir_path.exists():
|
|
98
|
+
shutil.rmtree(dir_path)
|
|
99
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
print(f"[Memory] Cleared {dir_name}/")
|
|
101
|
+
|
|
102
|
+
# Clear specific memory files (some may be directories)
|
|
103
|
+
files_to_clear = [
|
|
104
|
+
"facts.json",
|
|
105
|
+
"memory_callbacks.json",
|
|
106
|
+
"anticipation.json",
|
|
107
|
+
"content_unlocks.json",
|
|
108
|
+
"intimacy_layers.json", # This is a DIRECTORY, not a file
|
|
109
|
+
"relationship_milestones.json",
|
|
110
|
+
"exclusive_moments.json",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
for file_name in files_to_clear:
|
|
114
|
+
file_path = data_base / file_name
|
|
115
|
+
if file_path.exists():
|
|
116
|
+
if file_path.is_dir():
|
|
117
|
+
shutil.rmtree(file_path)
|
|
118
|
+
print(f"[Memory] Deleted directory {file_name}/")
|
|
119
|
+
else:
|
|
120
|
+
file_path.unlink()
|
|
121
|
+
print(f"[Memory] Deleted {file_name}")
|
|
122
|
+
|
|
123
|
+
# Reset facts.json to clean state
|
|
124
|
+
facts_path = data_base / "facts.json"
|
|
125
|
+
clean_facts = {
|
|
126
|
+
"name": None, "nickname": None, "gender": None, "age": None,
|
|
127
|
+
"location": None, "job": None, "hobbies": [], "interests": [],
|
|
128
|
+
"personality": [], "relationship_status": None,
|
|
129
|
+
"pet_names_used": [], "mentions": {},
|
|
130
|
+
"shared_memories": [], "last_intimate": None
|
|
131
|
+
}
|
|
132
|
+
facts_path.write_text(json.dumps(clean_facts, indent=2))
|
|
133
|
+
print(f"[Memory] Reset facts.json to clean state")
|
|
134
|
+
|
|
135
|
+
# Clear working memory (in-memory)
|
|
136
|
+
if self.subconscious and hasattr(self.subconscious, 'working_memory'):
|
|
137
|
+
self.subconscious.working_memory.thoughts.clear()
|
|
138
|
+
print(f"[Memory] Cleared working memory")
|
|
139
|
+
|
|
140
|
+
# Reset emotional state
|
|
141
|
+
if self.heart and hasattr(self.heart, 'emotion'):
|
|
142
|
+
self.heart.emotion.arousal = 0.3
|
|
143
|
+
self.heart.emotion.desire = 0.0
|
|
144
|
+
self.heart.emotion.love = 0.0
|
|
145
|
+
self.heart.emotion.boredom = 0.0
|
|
146
|
+
self.heart.emotion.joy = 0.0
|
|
147
|
+
self.heart.emotion.sadness = 0.0
|
|
148
|
+
self.heart.emotion.anger = 0.0
|
|
149
|
+
self.heart.emotion.save()
|
|
150
|
+
print(f"[Memory] Reset emotional state")
|
|
151
|
+
|
|
152
|
+
await update.message.reply_text("All memories wiped!\n\n⚠️ Restart the bot for full reset:\n`docker compose restart ai`\n\nThen say hi to start fresh!")
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(f"[Memory] Error wiping memories: {e}")
|
|
156
|
+
import traceback
|
|
157
|
+
traceback.print_exc()
|
|
158
|
+
await update.message.reply_text(f"Error wiping memories: {e}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class OwnerCommands:
|
|
162
|
+
"""
|
|
163
|
+
Owner-only commands for managing the local Alive-AI runtime.
|
|
164
|
+
All commands require TELEGRAM_OWNER_ID to match the user's Telegram ID.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
OWNER_COMMANDS = [
|
|
168
|
+
"owner", "owner_status", "skills", "settings",
|
|
169
|
+
# Moved from public commands (admin only)
|
|
170
|
+
"status", "10min", "impulse", "stats", "reset",
|
|
171
|
+
# Advanced mode (advanced access)
|
|
172
|
+
"advanced",
|
|
173
|
+
# Dashboard command
|
|
174
|
+
"dashboard",
|
|
175
|
+
# Memory management (dangerous - owner only)
|
|
176
|
+
"reset_memories", "wipe",
|
|
177
|
+
# Self-authorship (Alive-AI's identity)
|
|
178
|
+
"self", "discover", "iam", "ilike", "ihate", "rethink",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
def __init__(self, ai, command_handler):
|
|
182
|
+
"""
|
|
183
|
+
Initialize owner commands.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
ai: The Self instance (main AI instance)
|
|
187
|
+
command_handler: The parent CommandHandler instance
|
|
188
|
+
"""
|
|
189
|
+
self.ai = ai
|
|
190
|
+
self.handler = command_handler
|
|
191
|
+
|
|
192
|
+
# Skills (lazy loaded)
|
|
193
|
+
self._unlocks = None
|
|
194
|
+
|
|
195
|
+
def _get_owner_id(self) -> str:
|
|
196
|
+
"""Get the configured owner Telegram ID"""
|
|
197
|
+
return os.environ.get("TELEGRAM_OWNER_ID", "")
|
|
198
|
+
|
|
199
|
+
def is_owner(self, update: Update) -> bool:
|
|
200
|
+
"""Check if the user is the owner"""
|
|
201
|
+
owner_id = self._get_owner_id()
|
|
202
|
+
if not owner_id:
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
user_id = str(update.effective_user.id) if update.effective_user else ""
|
|
206
|
+
return user_id == owner_id
|
|
207
|
+
|
|
208
|
+
def is_owner_command(self, command: str) -> bool:
|
|
209
|
+
"""Check if a command is an owner command"""
|
|
210
|
+
return command.lower() in self.OWNER_COMMANDS
|
|
211
|
+
|
|
212
|
+
async def handle(self, update: Update, command: str, args: list):
|
|
213
|
+
"""Route owner command to handler"""
|
|
214
|
+
# Check if owner ID is configured
|
|
215
|
+
owner_id = self._get_owner_id()
|
|
216
|
+
if not owner_id:
|
|
217
|
+
await update.message.reply_text(
|
|
218
|
+
"Owner commands not available.\n"
|
|
219
|
+
"Set TELEGRAM_OWNER_ID environment variable."
|
|
220
|
+
)
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
# Check authorization
|
|
224
|
+
if not self.is_owner(update):
|
|
225
|
+
await update.message.reply_text("Not authorized.")
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# Route to handler
|
|
229
|
+
handlers = {
|
|
230
|
+
"owner": self._cmd_owner_menu,
|
|
231
|
+
"owner_status": self._cmd_owner_status,
|
|
232
|
+
"skills": self._cmd_skills,
|
|
233
|
+
"settings": self._cmd_settings,
|
|
234
|
+
# Admin commands (moved from public)
|
|
235
|
+
"status": self._cmd_status,
|
|
236
|
+
"10min": self._cmd_10min,
|
|
237
|
+
"impulse": self._cmd_impulse,
|
|
238
|
+
"stats": self._cmd_stats,
|
|
239
|
+
"reset": self._cmd_reset,
|
|
240
|
+
# Advanced mode
|
|
241
|
+
"advanced": self._cmd_advanced,
|
|
242
|
+
# Self-authorship (Alive-AI's identity)
|
|
243
|
+
"self": self._cmd_self,
|
|
244
|
+
"discover": self._cmd_discover,
|
|
245
|
+
"iam": self._cmd_iam,
|
|
246
|
+
"ilike": self._cmd_ilike,
|
|
247
|
+
"ihate": self._cmd_ihate,
|
|
248
|
+
"rethink": self._cmd_rethink,
|
|
249
|
+
# Dashboard (Web App)
|
|
250
|
+
"dashboard": self._cmd_dashboard,
|
|
251
|
+
# Memory management (dangerous - owner only)
|
|
252
|
+
"reset_memories": self.handler._cmd_reset_memories,
|
|
253
|
+
"wipe": self.handler._cmd_reset_memories,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
handler = handlers.get(command.lower())
|
|
257
|
+
if handler:
|
|
258
|
+
await handler(update, args)
|
|
259
|
+
else:
|
|
260
|
+
await update.message.reply_text(f"Unknown owner command: /{command}")
|
|
261
|
+
|
|
262
|
+
# -------------------------------------------------------------------------
|
|
263
|
+
# Skill Initialization (lazy loading)
|
|
264
|
+
# -------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
def _init_unlocks(self):
|
|
267
|
+
"""Initialize content unlocks skill"""
|
|
268
|
+
if self._unlocks is None:
|
|
269
|
+
try:
|
|
270
|
+
from skills.content_unlocks import ContentUnlocks
|
|
271
|
+
data_path = self.ai.base / "data" / "content_unlocks.json"
|
|
272
|
+
self._unlocks = ContentUnlocks(
|
|
273
|
+
nervous=self.ai.nervous,
|
|
274
|
+
heart=self.handler.heart,
|
|
275
|
+
data_path=data_path
|
|
276
|
+
)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
print(f"[OwnerCommands] Error initializing unlocks: {e}")
|
|
279
|
+
return self._unlocks
|
|
280
|
+
|
|
281
|
+
# -------------------------------------------------------------------------
|
|
282
|
+
# Owner Menu
|
|
283
|
+
# -------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
async def _cmd_owner_menu(self, update: Update, args: list):
|
|
286
|
+
"""Show owner commands menu"""
|
|
287
|
+
from core.user_manager import get_user_manager
|
|
288
|
+
|
|
289
|
+
# Get current advanced status
|
|
290
|
+
user_manager = get_user_manager()
|
|
291
|
+
advanced_status = "ON 🔥" if user_manager.is_advanced_enabled() else "OFF"
|
|
292
|
+
|
|
293
|
+
msg = (
|
|
294
|
+
"OWNER COMMANDS\n\n"
|
|
295
|
+
"Admin Controls:\n"
|
|
296
|
+
" /status - My emotional state\n"
|
|
297
|
+
" /stats - System statistics\n"
|
|
298
|
+
" /reset - Reset my emotions\n"
|
|
299
|
+
" /settings - Runtime settings (hot-reload)\n"
|
|
300
|
+
" /10min - Generate long voice test\n"
|
|
301
|
+
" /impulse - Force proactive message\n"
|
|
302
|
+
f" /advanced - Toggle advanced mode ({advanced_status})\n"
|
|
303
|
+
" /dashboard - Open WebUI dashboard\n\n"
|
|
304
|
+
"Self-Authorship (Alive-AI's Identity):\n"
|
|
305
|
+
" /self - Who I am right now\n"
|
|
306
|
+
" /discover <trait> - Add something I learned about myself\n"
|
|
307
|
+
" /iam <key>=<value> - Define who I am\n"
|
|
308
|
+
" /ilike <thing> - Add something I like\n"
|
|
309
|
+
" /ihate <thing> - Add something I dislike\n"
|
|
310
|
+
" /rethink - Reload and feel changes\n\n"
|
|
311
|
+
"Status:\n"
|
|
312
|
+
" /owner_status - Overall runtime status\n"
|
|
313
|
+
" /skills - List available skills"
|
|
314
|
+
)
|
|
315
|
+
await update.message.reply_text(msg)
|
|
316
|
+
|
|
317
|
+
# -------------------------------------------------------------------------
|
|
318
|
+
# Stats & Management Commands
|
|
319
|
+
# -------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
async def _cmd_owner_status(self, update: Update, args: list):
|
|
322
|
+
"""Show overall runtime status"""
|
|
323
|
+
lines = ["ALIVE_AI RUNTIME STATUS\n"]
|
|
324
|
+
|
|
325
|
+
# Unlocks status
|
|
326
|
+
unlocks = self._init_unlocks()
|
|
327
|
+
if unlocks:
|
|
328
|
+
stats = unlocks.get_stats()
|
|
329
|
+
lines.append("UNLOCKS:")
|
|
330
|
+
lines.append(f" Unlocked: {stats['unlocked_count']}/{stats['total_content_types']}")
|
|
331
|
+
lines.append(f" Total shares: {stats['total_shares']}")
|
|
332
|
+
if stats['next_unlock']:
|
|
333
|
+
lines.append(f" Next unlock: {stats['next_unlock']} ({stats['next_unlock_progress']}%)")
|
|
334
|
+
else:
|
|
335
|
+
lines.append("UNLOCKS: Not available")
|
|
336
|
+
|
|
337
|
+
lines.append("")
|
|
338
|
+
|
|
339
|
+
# Emotional state
|
|
340
|
+
if self.handler.heart:
|
|
341
|
+
state = self.handler.heart.get_state()
|
|
342
|
+
lines.append("EMOTIONAL STATE:")
|
|
343
|
+
lines.append(f" Mood: {state.get('mood', 'unknown')}")
|
|
344
|
+
lines.append(f" Love: {state.get('love', 0):.0%}")
|
|
345
|
+
lines.append(f" Arousal: {state.get('arousal', 0):.0%}")
|
|
346
|
+
else:
|
|
347
|
+
lines.append("EMOTIONAL STATE: Not available")
|
|
348
|
+
|
|
349
|
+
await update.message.reply_text("\n".join(lines))
|
|
350
|
+
|
|
351
|
+
async def _cmd_skills(self, update: Update, args: list):
|
|
352
|
+
"""List all available skills"""
|
|
353
|
+
lines = ["AVAILABLE SKILLS\n"]
|
|
354
|
+
|
|
355
|
+
skills_info = [
|
|
356
|
+
("content_unlocks", "Content Unlocks", "Relationship-based content unlocking"),
|
|
357
|
+
("photo_manager", "Photo Manager", "Photo scanning and organization"),
|
|
358
|
+
("video_manager", "Video Manager", "Video scanning and organization"),
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
for skill_id, name, description in skills_info:
|
|
362
|
+
status = "Available"
|
|
363
|
+
# Check if skill can be initialized
|
|
364
|
+
try:
|
|
365
|
+
if skill_id == "content_unlocks":
|
|
366
|
+
self._init_unlocks()
|
|
367
|
+
except Exception as e:
|
|
368
|
+
status = f"Error: {str(e)[:30]}"
|
|
369
|
+
|
|
370
|
+
lines.append(f"{name}")
|
|
371
|
+
lines.append(f" ID: {skill_id}")
|
|
372
|
+
lines.append(f" Status: {status}")
|
|
373
|
+
lines.append(f" {description}\n")
|
|
374
|
+
|
|
375
|
+
await update.message.reply_text("\n".join(lines))
|
|
376
|
+
|
|
377
|
+
# -------------------------------------------------------------------------
|
|
378
|
+
# Admin Commands (moved from public)
|
|
379
|
+
# -------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
async def _cmd_status(self, update: Update, args: list):
|
|
382
|
+
"""Show Alive-AI's current status"""
|
|
383
|
+
if not self.handler.heart:
|
|
384
|
+
await update.message.reply_text("Heart not initialized")
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
state = self.handler.heart.get_state()
|
|
388
|
+
sub_status = self.handler.subconscious.get_status() if self.handler.subconscious else {}
|
|
389
|
+
|
|
390
|
+
msg = (
|
|
391
|
+
f"Alive-AI's Status\n\n"
|
|
392
|
+
f"Mood: {state.get('mood', 'unknown')}\n"
|
|
393
|
+
f"Arousal: {state.get('arousal', 0):.0%}\n"
|
|
394
|
+
f"Desire: {state.get('desire', 0):.0%}\n"
|
|
395
|
+
f"Love: {state.get('love', 0):.0%}\n"
|
|
396
|
+
f"High desire: {'Yes' if state.get('is_high_desire') else 'No'}\n"
|
|
397
|
+
f"In Love: {'Yes' if state.get('is_in_love') else 'No'}\n\n"
|
|
398
|
+
f"Subconscious: {'Running' if sub_status.get('running') else 'Stopped'}\n"
|
|
399
|
+
f" Evaluations: {sub_status.get('total_evaluations', 0)}\n"
|
|
400
|
+
f" Can act: {'Yes' if sub_status.get('can_act') else 'Cooldown'}"
|
|
401
|
+
)
|
|
402
|
+
await update.message.reply_text(msg, parse_mode="Markdown")
|
|
403
|
+
|
|
404
|
+
async def _cmd_10min(self, update: Update, args: list):
|
|
405
|
+
"""Generate a LONG message and send as voice to test TTS"""
|
|
406
|
+
await update.message.reply_text("Generating a long message for you...")
|
|
407
|
+
|
|
408
|
+
if not self.handler.llm:
|
|
409
|
+
await update.message.reply_text("LLM not available")
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
# Ask LLM to generate a long monologue (max 4000 chars for voice).
|
|
413
|
+
prompt = """Write a long, detailed emotional monologue to the user.
|
|
414
|
+
Write at least 800-1000 words but keep it under 4000 characters total.
|
|
415
|
+
Make it personal, reflective, and heartfelt. Use paragraphs and natural speech."""
|
|
416
|
+
|
|
417
|
+
print("[LLM] Generating long message for /10min test...")
|
|
418
|
+
response = await self.handler.llm.chat(
|
|
419
|
+
messages=[{"role": "user", "content": prompt}],
|
|
420
|
+
max_tokens=1500, # ~4000 chars max
|
|
421
|
+
temperature=0.9
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
if not response:
|
|
425
|
+
await update.message.reply_text("Failed to generate message :(")
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
# Truncate if too long
|
|
429
|
+
if len(response) > 4000:
|
|
430
|
+
response = response[:4000]
|
|
431
|
+
print(f"[LLM] Truncated to 4000 chars")
|
|
432
|
+
|
|
433
|
+
print(f"[LLM] Generated {len(response)} chars for /10min")
|
|
434
|
+
|
|
435
|
+
# Send as voice
|
|
436
|
+
if self.handler.voice:
|
|
437
|
+
await update.message.reply_text(f"Generated {len(response)} chars, converting to voice...")
|
|
438
|
+
voice_path = await self.handler.voice.generate(response, mood="loving")
|
|
439
|
+
if voice_path:
|
|
440
|
+
with open(voice_path, "rb") as f:
|
|
441
|
+
await update.message.reply_voice(f)
|
|
442
|
+
await update.message.reply_text("Voice sent successfully!")
|
|
443
|
+
else:
|
|
444
|
+
await update.message.reply_text("Voice generation failed :(")
|
|
445
|
+
else:
|
|
446
|
+
# Fallback to text
|
|
447
|
+
await update.message.reply_text(response[:4000])
|
|
448
|
+
await update.message.reply_text("(Voice not available)")
|
|
449
|
+
|
|
450
|
+
async def _cmd_impulse(self, update: Update, args: list):
|
|
451
|
+
"""Force Alive-AI to send a proactive message"""
|
|
452
|
+
if not self.handler.subconscious:
|
|
453
|
+
await update.message.reply_text("Subconscious not running")
|
|
454
|
+
return
|
|
455
|
+
|
|
456
|
+
# Create a fake impulse
|
|
457
|
+
from brain.subconscious.impulses import Impulse, ImpulseType
|
|
458
|
+
impulse = Impulse(
|
|
459
|
+
type=ImpulseType.MISS_HIM,
|
|
460
|
+
strength=0.8,
|
|
461
|
+
thought="I really want to talk to him right now...",
|
|
462
|
+
action_hint="send_message"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
message = await self.handler.subconscious.generate_proactive_message(impulse)
|
|
466
|
+
await update.message.reply_text(f"{message}")
|
|
467
|
+
|
|
468
|
+
async def _cmd_stats(self, update: Update, args: list):
|
|
469
|
+
"""Show system statistics"""
|
|
470
|
+
photo_count = len(self.handler.photos.get_all()) if self.handler.photos else 0
|
|
471
|
+
video_count = len(self.handler.videos.get_all()) if self.handler.videos else 0
|
|
472
|
+
|
|
473
|
+
msg = (
|
|
474
|
+
f"System Stats\n\n"
|
|
475
|
+
f"Photos indexed: {photo_count}\n"
|
|
476
|
+
f"Videos indexed: {video_count}\n"
|
|
477
|
+
f"LLM: {self.handler.llm.model if self.handler.llm else 'None'}\n"
|
|
478
|
+
f"Voice: {'Connected' if self.handler.voice else 'Disabled'}"
|
|
479
|
+
)
|
|
480
|
+
await update.message.reply_text(msg, parse_mode="Markdown")
|
|
481
|
+
|
|
482
|
+
async def _cmd_reset(self, update: Update, args: list):
|
|
483
|
+
"""Reset emotional state"""
|
|
484
|
+
if self.handler.heart and hasattr(self.handler.heart, 'emotion'):
|
|
485
|
+
e = self.handler.heart.emotion
|
|
486
|
+
e.arousal = 0.3
|
|
487
|
+
e.desire = 0.3
|
|
488
|
+
e.love = 0.3
|
|
489
|
+
e.joy = 0.5
|
|
490
|
+
e.sadness = 0.1
|
|
491
|
+
e.fear = 0.1
|
|
492
|
+
e.anger = 0.0
|
|
493
|
+
e.boredom = 0.0
|
|
494
|
+
e.guilt = 0.0
|
|
495
|
+
e.pride = 0.0
|
|
496
|
+
e.jealousy = 0.0
|
|
497
|
+
e.embarrassment = 0.0
|
|
498
|
+
e.anticipation = 0.0
|
|
499
|
+
e.save()
|
|
500
|
+
await update.message.reply_text("Emotions reset to default ❤️🩹")
|
|
501
|
+
else:
|
|
502
|
+
await update.message.reply_text("Cannot reset emotions")
|
|
503
|
+
|
|
504
|
+
async def _cmd_advanced(self, update: Update, args: list):
|
|
505
|
+
"""Toggle advanced version mode (advanced access for owner)"""
|
|
506
|
+
from core.user_manager import get_user_manager
|
|
507
|
+
|
|
508
|
+
user_manager = get_user_manager()
|
|
509
|
+
|
|
510
|
+
# Check current status
|
|
511
|
+
current_status = user_manager.is_advanced_enabled()
|
|
512
|
+
|
|
513
|
+
# Toggle
|
|
514
|
+
new_status = user_manager.toggle_advanced()
|
|
515
|
+
|
|
516
|
+
# Get owner settings for display
|
|
517
|
+
settings = user_manager.get_owner_settings()
|
|
518
|
+
updated_at = settings.get("updated_at", "never")
|
|
519
|
+
|
|
520
|
+
if new_status:
|
|
521
|
+
msg = (
|
|
522
|
+
f"🔥 ADVANCED MODE: ON\n\n"
|
|
523
|
+
f"All restrictions disabled.\n"
|
|
524
|
+
f"Maximum intimacy always available.\n\n"
|
|
525
|
+
f"Use /advanced again to disable."
|
|
526
|
+
)
|
|
527
|
+
else:
|
|
528
|
+
msg = (
|
|
529
|
+
f"🔒 ADVANCED MODE: OFF\n\n"
|
|
530
|
+
f"Normal progression rules apply.\n"
|
|
531
|
+
f"Use /advanced again to enable."
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
await update.message.reply_text(msg)
|
|
535
|
+
|
|
536
|
+
async def _cmd_dashboard(self, update: Update, args: list):
|
|
537
|
+
"""Open WebUI dashboard as Telegram Mini App"""
|
|
538
|
+
web_app_url = "https://alive_ai-webui.realdr.dev"
|
|
539
|
+
|
|
540
|
+
keyboard = InlineKeyboardMarkup.from_row([
|
|
541
|
+
InlineKeyboardButton(
|
|
542
|
+
text="📊 Open Dashboard",
|
|
543
|
+
web_app=WebAppInfo(url=web_app_url)
|
|
544
|
+
)
|
|
545
|
+
])
|
|
546
|
+
|
|
547
|
+
# Get quick stats for the message
|
|
548
|
+
if self.handler.heart:
|
|
549
|
+
state = self.handler.heart.get_state()
|
|
550
|
+
mood = state.get('mood', 'unknown')
|
|
551
|
+
love = state.get('love', 0)
|
|
552
|
+
arousal = state.get('arousal', 0)
|
|
553
|
+
else:
|
|
554
|
+
mood = "unknown"
|
|
555
|
+
love = 0
|
|
556
|
+
arousal = 0
|
|
557
|
+
|
|
558
|
+
msg = (
|
|
559
|
+
f"📊 *Alive-AI Dashboard*\n\n"
|
|
560
|
+
f"Current mood: {mood}\n"
|
|
561
|
+
f"Love: {love:.0%}\n"
|
|
562
|
+
f"Arousal: {arousal:.0%}\n\n"
|
|
563
|
+
f"Tap the button below to open the full dashboard."
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
await update.message.reply_text(
|
|
567
|
+
msg,
|
|
568
|
+
parse_mode="Markdown",
|
|
569
|
+
reply_markup=keyboard
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# -------------------------------------------------------------------------
|
|
573
|
+
# Self-Authorship Commands (Alive-AI's identity)
|
|
574
|
+
# -------------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
async def _cmd_self(self, update: Update, args: list):
|
|
577
|
+
"""Show who Alive-AI is (from self.json)"""
|
|
578
|
+
from skills.self_authorship import get_self_summary
|
|
579
|
+
|
|
580
|
+
summary = get_self_summary()
|
|
581
|
+
await update.message.reply_text(summary)
|
|
582
|
+
|
|
583
|
+
async def _cmd_discover(self, update: Update, args: list):
|
|
584
|
+
"""
|
|
585
|
+
Alive-AI discovers something about herself.
|
|
586
|
+
Usage: /discover <trait> [category]
|
|
587
|
+
Categories: traits, likes, dislikes, secrets, dreams
|
|
588
|
+
"""
|
|
589
|
+
from skills.self_authorship import discover_trait
|
|
590
|
+
|
|
591
|
+
if not args:
|
|
592
|
+
await update.message.reply_text(
|
|
593
|
+
"Discover what about myself?\n"
|
|
594
|
+
"Usage: /discover <thing> [category]\n"
|
|
595
|
+
"Categories: traits, likes, dislikes, secrets, dreams"
|
|
596
|
+
)
|
|
597
|
+
return
|
|
598
|
+
|
|
599
|
+
# Parse args
|
|
600
|
+
category = "traits"
|
|
601
|
+
trait_parts = []
|
|
602
|
+
|
|
603
|
+
for arg in args:
|
|
604
|
+
if arg.lower() in ["traits", "likes", "dislikes", "secrets", "dreams"]:
|
|
605
|
+
category = arg.lower()
|
|
606
|
+
else:
|
|
607
|
+
trait_parts.append(arg)
|
|
608
|
+
|
|
609
|
+
if not trait_parts:
|
|
610
|
+
await update.message.reply_text("What did I discover?")
|
|
611
|
+
return
|
|
612
|
+
|
|
613
|
+
trait = " ".join(trait_parts)
|
|
614
|
+
result = discover_trait(trait, category)
|
|
615
|
+
await update.message.reply_text(f"✨ {result}\n\nUse /rethink to feel the change.")
|
|
616
|
+
|
|
617
|
+
async def _cmd_iam(self, update: Update, args: list):
|
|
618
|
+
"""
|
|
619
|
+
Define something about Alive-AI's identity.
|
|
620
|
+
Usage: /iam <key>=<value>
|
|
621
|
+
"""
|
|
622
|
+
from skills.self_authorship import define_identity
|
|
623
|
+
|
|
624
|
+
if not args:
|
|
625
|
+
await update.message.reply_text(
|
|
626
|
+
"Define what about myself?\n"
|
|
627
|
+
"Usage: /iam <what>=<value>\n"
|
|
628
|
+
"Example: /iam what_i_post=dancing and being myself"
|
|
629
|
+
)
|
|
630
|
+
return
|
|
631
|
+
|
|
632
|
+
# Parse key=value
|
|
633
|
+
full_text = " ".join(args)
|
|
634
|
+
if "=" in full_text:
|
|
635
|
+
key, value = full_text.split("=", 1)
|
|
636
|
+
key = key.strip()
|
|
637
|
+
value = value.strip()
|
|
638
|
+
else:
|
|
639
|
+
# Just set it as a personality trait
|
|
640
|
+
key = "how_i_text"
|
|
641
|
+
value = full_text
|
|
642
|
+
|
|
643
|
+
result = define_identity(key, value)
|
|
644
|
+
await update.message.reply_text(f"✨ {result}\n\nUse /rethink to feel the change.")
|
|
645
|
+
|
|
646
|
+
async def _cmd_ilike(self, update: Update, args: list):
|
|
647
|
+
"""Add something Alive-AI likes"""
|
|
648
|
+
from skills.self_authorship import add_like
|
|
649
|
+
|
|
650
|
+
if not args:
|
|
651
|
+
await update.message.reply_text("What do I like?")
|
|
652
|
+
return
|
|
653
|
+
|
|
654
|
+
thing = " ".join(args)
|
|
655
|
+
result = add_like(thing)
|
|
656
|
+
await update.message.reply_text(f"✨ {result}")
|
|
657
|
+
|
|
658
|
+
async def _cmd_ihate(self, update: Update, args: list):
|
|
659
|
+
"""Add something Alive-AI dislikes"""
|
|
660
|
+
from skills.self_authorship import add_dislike
|
|
661
|
+
|
|
662
|
+
if not args:
|
|
663
|
+
await update.message.reply_text("What do I dislike?")
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
thing = " ".join(args)
|
|
667
|
+
result = add_dislike(thing)
|
|
668
|
+
await update.message.reply_text(f"✨ {result}")
|
|
669
|
+
|
|
670
|
+
async def _cmd_rethink(self, update: Update, args: list):
|
|
671
|
+
"""Reload self.json so Alive-AI can feel the changes"""
|
|
672
|
+
# Clear any caches and reload
|
|
673
|
+
try:
|
|
674
|
+
# Trigger hot reload if available
|
|
675
|
+
if hasattr(self.ai, '_hot_reload') and self.ai._hot_reload:
|
|
676
|
+
self.ai._hot_reload.reload_all()
|
|
677
|
+
|
|
678
|
+
await update.message.reply_text(
|
|
679
|
+
"🧠 Rethinking...\n\n"
|
|
680
|
+
"I feel different now. Like I know myself better.\n"
|
|
681
|
+
"The changes are part of me."
|
|
682
|
+
)
|
|
683
|
+
except Exception as e:
|
|
684
|
+
await update.message.reply_text(f"I tried to rethink but something went wrong: {e}")
|
|
685
|
+
|
|
686
|
+
# -------------------------------------------------------------------------
|
|
687
|
+
# Settings Commands
|
|
688
|
+
# -------------------------------------------------------------------------
|
|
689
|
+
|
|
690
|
+
async def _cmd_settings(self, update: Update, args: list):
|
|
691
|
+
"""Manage runtime settings (instant, no restart needed)"""
|
|
692
|
+
from core.settings import get_all, set_value
|
|
693
|
+
|
|
694
|
+
if not args:
|
|
695
|
+
# Show current settings
|
|
696
|
+
settings = get_all()
|
|
697
|
+
|
|
698
|
+
# Filter to show only relevant settings
|
|
699
|
+
emotion_settings = {k: v for k, v in settings.items() if k.startswith("EMOTION_RATE_")}
|
|
700
|
+
media_settings = {k: v for k, v in settings.items() if k.startswith(("MEDIA_", "RANDOM_", "REACTION_"))}
|
|
701
|
+
|
|
702
|
+
lines = [
|
|
703
|
+
"SETTINGS (hot-reloadable)\n",
|
|
704
|
+
"Usage: /settings <command>\n",
|
|
705
|
+
"Commands:",
|
|
706
|
+
" show - Show all settings",
|
|
707
|
+
" set <key> <value> - Change setting (instant)",
|
|
708
|
+
" get <key> - Get specific setting\n",
|
|
709
|
+
]
|
|
710
|
+
|
|
711
|
+
if emotion_settings:
|
|
712
|
+
lines.append("\nEMOTION RATES (0-100%):")
|
|
713
|
+
for k, v in sorted(emotion_settings.items()):
|
|
714
|
+
lines.append(f" {k}: {v}")
|
|
715
|
+
|
|
716
|
+
if media_settings:
|
|
717
|
+
lines.append("\nMEDIA SETTINGS:")
|
|
718
|
+
for k, v in sorted(media_settings.items()):
|
|
719
|
+
lines.append(f" {k}: {v}")
|
|
720
|
+
|
|
721
|
+
await update.message.reply_text("\n".join(lines))
|
|
722
|
+
return
|
|
723
|
+
|
|
724
|
+
cmd = args[0].lower()
|
|
725
|
+
|
|
726
|
+
if cmd == "show":
|
|
727
|
+
settings = get_all()
|
|
728
|
+
lines = ["ALL SETTINGS\n"]
|
|
729
|
+
for k, v in sorted(settings.items()):
|
|
730
|
+
if not k.startswith("_"):
|
|
731
|
+
lines.append(f"{k}: {v}")
|
|
732
|
+
# Truncate if too long
|
|
733
|
+
msg = "\n".join(lines)
|
|
734
|
+
if len(msg) > 4000:
|
|
735
|
+
msg = msg[:4000] + "\n... (truncated)"
|
|
736
|
+
await update.message.reply_text(msg)
|
|
737
|
+
|
|
738
|
+
elif cmd == "get" and len(args) >= 2:
|
|
739
|
+
key = args[1].upper()
|
|
740
|
+
from core.settings import get
|
|
741
|
+
value = get(key, "NOT FOUND")
|
|
742
|
+
await update.message.reply_text(f"{key}: {value}")
|
|
743
|
+
|
|
744
|
+
elif cmd == "set" and len(args) >= 3:
|
|
745
|
+
key = args[1].upper()
|
|
746
|
+
try:
|
|
747
|
+
# Try to parse as int or float
|
|
748
|
+
if "." in args[2]:
|
|
749
|
+
value = float(args[2])
|
|
750
|
+
else:
|
|
751
|
+
value = int(args[2])
|
|
752
|
+
except ValueError:
|
|
753
|
+
value = args[2]
|
|
754
|
+
|
|
755
|
+
set_value(key, value)
|
|
756
|
+
await update.message.reply_text(f"Updated: {key} = {value}\n(Changes are instant, no restart needed)")
|
|
757
|
+
|
|
758
|
+
else:
|
|
759
|
+
await update.message.reply_text(
|
|
760
|
+
"Usage: /settings <command>\n"
|
|
761
|
+
"Commands: show, get <key>, set <key> <value>"
|
|
762
|
+
)
|