aetherforge-platform 1.0.0__py3-none-any.whl

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 (55) hide show
  1. aetherforge_platform-1.0.0.dist-info/METADATA +86 -0
  2. aetherforge_platform-1.0.0.dist-info/RECORD +55 -0
  3. aetherforge_platform-1.0.0.dist-info/WHEEL +5 -0
  4. aetherforge_platform-1.0.0.dist-info/top_level.txt +4 -0
  5. ai-life-assistant-copy/ai_agent.py +145 -0
  6. ai-life-assistant-copy/avatar_manager.py +231 -0
  7. ai-life-assistant-copy/avatar_packer.py +261 -0
  8. ai-life-assistant-copy/backup_all.py +262 -0
  9. ai-life-assistant-copy/backups/backup_20260404_193836/ai_agent.py +145 -0
  10. ai-life-assistant-copy/backups/backup_20260404_193836/avatar_manager.py +231 -0
  11. ai-life-assistant-copy/backups/backup_20260404_193836/avatar_packer.py +261 -0
  12. ai-life-assistant-copy/backups/backup_20260404_193836/backup_all.py +262 -0
  13. ai-life-assistant-copy/backups/backup_20260404_193836/commands.py +210 -0
  14. ai-life-assistant-copy/backups/backup_20260404_193836/config.py +30 -0
  15. ai-life-assistant-copy/backups/backup_20260404_193836/daemon/__init__.py +3 -0
  16. ai-life-assistant-copy/backups/backup_20260404_193836/daemon/daemon.py +174 -0
  17. ai-life-assistant-copy/backups/backup_20260404_193836/database.py +292 -0
  18. ai-life-assistant-copy/backups/backup_20260404_193836/graph.py +531 -0
  19. ai-life-assistant-copy/backups/backup_20260404_193836/main.py +830 -0
  20. ai-life-assistant-copy/backups/backup_20260404_193836/mcp_tools.py +449 -0
  21. ai-life-assistant-copy/backups/backup_20260404_193836/memory.py +92 -0
  22. ai-life-assistant-copy/backups/backup_20260404_193836/memory_v2.py +333 -0
  23. ai-life-assistant-copy/backups/backup_20260404_193836/mock_shopping_data.py +172 -0
  24. ai-life-assistant-copy/backups/backup_20260404_193836/personality.py +159 -0
  25. ai-life-assistant-copy/backups/backup_20260404_193836/speech.py +41 -0
  26. ai-life-assistant-copy/backups/backup_20260404_193836/test_simple.py +127 -0
  27. ai-life-assistant-copy/backups/backup_20260404_193836/tools/__init__.py +15 -0
  28. ai-life-assistant-copy/backups/backup_20260404_193836/tools/amazon_tool.py +103 -0
  29. ai-life-assistant-copy/backups/backup_20260404_193836/tools/calendar_tool.py +92 -0
  30. ai-life-assistant-copy/backups/backup_20260404_193836/tools/reminder_tool.py +92 -0
  31. ai-life-assistant-copy/backups/backup_20260404_193836/tools/weather_tool.py +45 -0
  32. ai-life-assistant-copy/backups/backup_20260404_193836/tree_memory.py +340 -0
  33. ai-life-assistant-copy/commands.py +210 -0
  34. ai-life-assistant-copy/config.py +30 -0
  35. ai-life-assistant-copy/daemon/__init__.py +3 -0
  36. ai-life-assistant-copy/daemon/daemon.py +174 -0
  37. ai-life-assistant-copy/database.py +292 -0
  38. ai-life-assistant-copy/graph.py +531 -0
  39. ai-life-assistant-copy/main.py +830 -0
  40. ai-life-assistant-copy/mcp_tools.py +449 -0
  41. ai-life-assistant-copy/memory.py +92 -0
  42. ai-life-assistant-copy/memory_v2.py +333 -0
  43. ai-life-assistant-copy/mock_shopping_data.py +172 -0
  44. ai-life-assistant-copy/personality.py +159 -0
  45. ai-life-assistant-copy/speech.py +41 -0
  46. ai-life-assistant-copy/test_simple.py +127 -0
  47. ai-life-assistant-copy/tools/__init__.py +15 -0
  48. ai-life-assistant-copy/tools/amazon_tool.py +103 -0
  49. ai-life-assistant-copy/tools/calendar_tool.py +92 -0
  50. ai-life-assistant-copy/tools/reminder_tool.py +92 -0
  51. ai-life-assistant-copy/tools/weather_tool.py +45 -0
  52. ai-life-assistant-copy/tree_memory.py +340 -0
  53. ai_agent_runtime.py +447 -0
  54. main.py +6752 -0
  55. mcp_server.py +427 -0
@@ -0,0 +1,830 @@
1
+ from fastapi import FastAPI, HTTPException, UploadFile, File
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import FileResponse
5
+ from pydantic import BaseModel
6
+ from typing import List, Dict, Any, Optional
7
+ import os
8
+ from graph import run_chat
9
+ from config import APP_NAME, APP_VERSION, DEBUG
10
+ from personality import personality_analyzer
11
+ from ai_agent import agent_system
12
+ from speech import speech_recognition
13
+ from mcp_tools import mcp_tool_manager
14
+ from avatar_manager import avatar_manager
15
+ from avatar_packer import avatar_packer, marketplace
16
+ from tree_memory import tree_memory_system
17
+ from database import avatar_database
18
+
19
+ app = FastAPI(
20
+ title=APP_NAME,
21
+ version=APP_VERSION,
22
+ debug=DEBUG
23
+ )
24
+
25
+ app.add_middleware(
26
+ CORSMiddleware,
27
+ allow_origins=["*"],
28
+ allow_credentials=True,
29
+ allow_methods=["*"],
30
+ allow_headers=["*"],
31
+ )
32
+
33
+ class ChatRequest(BaseModel):
34
+ message: str
35
+ user_id: Optional[str] = "default"
36
+ conversation_history: Optional[List[Dict[str, str]]] = None
37
+
38
+ class ChatResponse(BaseModel):
39
+ success: bool
40
+ results: List[Dict[str, Any]]
41
+ cards: List[Dict[str, Any]]
42
+ visualization: Optional[str]
43
+ intent: Optional[str]
44
+ status: str
45
+ limit_message: Optional[str] = None
46
+
47
+ class PersonalityAnalysisRequest(BaseModel):
48
+ user_id: str
49
+ messages: List[Dict[str, str]]
50
+
51
+ class MatchRequest(BaseModel):
52
+ user1_id: str
53
+ user2_id: str
54
+
55
+ @app.get("/")
56
+ async def root():
57
+ index_path = os.path.join(os.path.dirname(__file__), "static", "index.html")
58
+ if os.path.exists(index_path):
59
+ return FileResponse(index_path)
60
+ return {
61
+ "name": APP_NAME,
62
+ "version": APP_VERSION,
63
+ "status": "running"
64
+ }
65
+
66
+ @app.get("/profile")
67
+ async def profile_page():
68
+ profile_path = os.path.join(os.path.dirname(__file__), "static", "profile.html")
69
+ if os.path.exists(profile_path):
70
+ return FileResponse(profile_path)
71
+ raise HTTPException(status_code=404, detail="Profile page not found")
72
+
73
+ @app.get("/test-products")
74
+ async def test_products_page():
75
+ test_path = os.path.join(os.path.dirname(__file__), "static", "test_products.html")
76
+ if os.path.exists(test_path):
77
+ return FileResponse(test_path)
78
+ raise HTTPException(status_code=404, detail="Test products page not found")
79
+
80
+ @app.get("/health")
81
+ async def health_check():
82
+ return {"status": "healthy"}
83
+
84
+ # ============================================
85
+ # Usage Tracking & Subscription System
86
+ # ============================================
87
+
88
+ # Usage tracking system
89
+ usage_stats = {
90
+ "default": {
91
+ "messages_sent": 0,
92
+ "tokens_used": 0,
93
+ "last_reset": "2025-01-01"
94
+ }
95
+ }
96
+
97
+ # Demo subscription plans (for demonstration)
98
+ subscription_plans = {
99
+ "free": {
100
+ "name": "Free",
101
+ "daily_limit": 10,
102
+ "monthly_limit": 100,
103
+ "features": ["Basic chat", "10 messages/day"]
104
+ },
105
+ "pro": {
106
+ "name": "Pro",
107
+ "daily_limit": 100,
108
+ "monthly_limit": 3000,
109
+ "features": ["Advanced chat", "100 messages/day", "Priority support"]
110
+ },
111
+ "unlimited": {
112
+ "name": "Unlimited",
113
+ "daily_limit": None,
114
+ "monthly_limit": None,
115
+ "features": ["Unlimited chat", "All features", "Premium support"]
116
+ }
117
+ }
118
+
119
+ current_plan = "free" # Demo default plan
120
+
121
+ @app.get("/api/usage")
122
+ async def get_usage(user_id: str = "default"):
123
+ """Get usage statistics for a user"""
124
+ if user_id not in usage_stats:
125
+ usage_stats[user_id] = {
126
+ "messages_sent": 0,
127
+ "tokens_used": 0,
128
+ "last_reset": "2025-01-01"
129
+ }
130
+
131
+ return {
132
+ "success": True,
133
+ "usage": usage_stats[user_id],
134
+ "plan": subscription_plans[current_plan],
135
+ "current_plan": current_plan
136
+ }
137
+
138
+ @app.post("/api/usage/reset")
139
+ async def reset_usage(user_id: str = "default"):
140
+ """Reset usage statistics (demo only)"""
141
+ if user_id in usage_stats:
142
+ usage_stats[user_id]["messages_sent"] = 0
143
+ usage_stats[user_id]["tokens_used"] = 0
144
+
145
+ return {"success": True, "message": "Usage reset"}
146
+
147
+ @app.get("/api/plans")
148
+ async def get_plans():
149
+ """Get available subscription plans"""
150
+ return {
151
+ "success": True,
152
+ "plans": subscription_plans,
153
+ "current_plan": current_plan
154
+ }
155
+
156
+ def check_limit(user_id: str) -> tuple[bool, str]:
157
+ """
158
+ Check if user has exceeded their usage limit
159
+ Returns (allowed, message)
160
+ """
161
+ if current_plan == "unlimited":
162
+ return (True, "")
163
+
164
+ if user_id not in usage_stats:
165
+ usage_stats[user_id] = {
166
+ "messages_sent": 0,
167
+ "tokens_used": 0,
168
+ "last_reset": "2025-01-01"
169
+ }
170
+
171
+ plan = subscription_plans[current_plan]
172
+ messages_sent = usage_stats[user_id]["messages_sent"]
173
+
174
+ if plan["daily_limit"] and messages_sent >= plan["daily_limit"]:
175
+ return (False, f"Daily limit reached ({plan['daily_limit']} messages). Upgrade to Pro for more!")
176
+
177
+ return (True, "")
178
+
179
+ def increment_usage(user_id: str):
180
+ """Increment usage counter"""
181
+ if user_id not in usage_stats:
182
+ usage_stats[user_id] = {
183
+ "messages_sent": 0,
184
+ "tokens_used": 0,
185
+ "last_reset": "2025-01-01"
186
+ }
187
+
188
+ usage_stats[user_id]["messages_sent"] += 1
189
+
190
+ # ============================================
191
+ # Chat Endpoint
192
+ # ============================================
193
+
194
+ @app.post("/api/chat", response_model=ChatResponse)
195
+ async def chat_endpoint(request: ChatRequest):
196
+ try:
197
+ user_id = request.user_id or "default"
198
+
199
+ # Check usage limit
200
+ allowed, limit_message = check_limit(user_id)
201
+ if not allowed:
202
+ return ChatResponse(
203
+ success=False,
204
+ results=[],
205
+ cards=[],
206
+ visualization=None,
207
+ intent=None,
208
+ status="limit_reached",
209
+ limit_message=limit_message
210
+ )
211
+
212
+ # Increment usage counter
213
+ increment_usage(user_id)
214
+
215
+ # Run chat
216
+ result = run_chat(
217
+ message=request.message,
218
+ user_id=user_id,
219
+ conversation_history=request.conversation_history
220
+ )
221
+
222
+ return ChatResponse(
223
+ success=True,
224
+ results=result.get("results", []),
225
+ cards=result.get("cards", []),
226
+ visualization=result.get("visualization"),
227
+ intent=result.get("intent"),
228
+ status=result.get("status", "success")
229
+ )
230
+
231
+ except Exception as e:
232
+ print(f"Chat error: {e}")
233
+ raise HTTPException(
234
+ status_code=500,
235
+ detail="Internal server error"
236
+ )
237
+
238
+ @app.get("/api/test")
239
+ async def test_endpoint():
240
+ return {
241
+ "message": "Test endpoint working!",
242
+ "timestamp": "now"
243
+ }
244
+
245
+ @app.post("/api/personality/analyze")
246
+ async def analyze_personality(request: PersonalityAnalysisRequest):
247
+ try:
248
+ analysis = personality_analyzer.analyze_conversation(
249
+ user_id=request.user_id,
250
+ messages=request.messages
251
+ )
252
+ return {"success": True, "analysis": analysis}
253
+ except Exception as e:
254
+ print(f"Personality analysis error: {e}")
255
+ raise HTTPException(status_code=500, detail="Analysis failed")
256
+
257
+ @app.get("/api/personality/{user_id}")
258
+ async def get_personality(user_id: str):
259
+ analysis = personality_analyzer.get_analysis(user_id)
260
+ if analysis:
261
+ return {"success": True, "analysis": analysis}
262
+ return {"success": False, "message": "No analysis found"}
263
+
264
+ @app.post("/api/ai-chat/start")
265
+ async def start_ai_chat(request: MatchRequest):
266
+ try:
267
+ result = agent_system.start_ai_chat(
268
+ user1_id=request.user1_id,
269
+ user2_id=request.user2_id
270
+ )
271
+ return result
272
+ except Exception as e:
273
+ print(f"AI chat error: {e}")
274
+ raise HTTPException(status_code=500, detail="AI chat failed")
275
+
276
+ @app.post("/api/speech/transcribe")
277
+ async def transcribe_speech(file: UploadFile = File(...)):
278
+ try:
279
+ text = await speech_recognition.transcribe_audio(file)
280
+ if text:
281
+ return {"success": True, "text": text}
282
+ return {"success": False, "message": "Speech recognition not available"}
283
+ except Exception as e:
284
+ print(f"Speech transcription error: {e}")
285
+ raise HTTPException(status_code=500, detail="Transcription failed")
286
+
287
+ @app.get("/api/tools/list")
288
+ async def list_tools():
289
+ tools = mcp_tool_manager.list_tools()
290
+ return {
291
+ "success": True,
292
+ "tools": [
293
+ {
294
+ "name": t.name,
295
+ "description": t.description,
296
+ "type": t.type.value,
297
+ "parameters": [
298
+ {"name": p.name, "type": p.type, "description": p.description, "required": p.required}
299
+ for p in t.parameters
300
+ ]
301
+ }
302
+ for t in tools
303
+ ]
304
+ }
305
+
306
+ class ToolExecuteRequest(BaseModel):
307
+ tool_name: str
308
+ params: Dict[str, Any]
309
+
310
+ class TitleGenerateRequest(BaseModel):
311
+ message: str
312
+ conversation_history: Optional[List[Dict[str, str]]] = None
313
+
314
+ @app.post("/api/tools/execute")
315
+ async def execute_tool(request: ToolExecuteRequest):
316
+ try:
317
+ result = await mcp_tool_manager.execute_tool(
318
+ tool_name=request.tool_name,
319
+ params=request.params
320
+ )
321
+ return result
322
+ except Exception as e:
323
+ print(f"Tool execution error: {e}")
324
+ raise HTTPException(status_code=500, detail="Tool execution failed")
325
+
326
+ @app.post("/api/chat/title")
327
+ async def generate_chat_title(request: TitleGenerateRequest):
328
+ """
329
+ Generate a session title using AI (Claude/Haiku model).
330
+
331
+ This is the single source of truth for AI-generated session titles.
332
+ Follows the same pattern as Claude Code's sessionTitle.ts
333
+
334
+ Args:
335
+ request: TitleGenerateRequest with user's first message
336
+
337
+ Returns:
338
+ JSON with generated title or fallback
339
+
340
+ Example response:
341
+ {"success": true, "title": "Fix login button on mobile"}
342
+ """
343
+ try:
344
+ # Check if API key is configured
345
+ if not GROK_API_KEY:
346
+ print("[Title API] No API key configured, using fallback")
347
+ return {
348
+ "success": False,
349
+ "title": generate_fallback_title(request.message),
350
+ "method": "fallback"
351
+ }
352
+
353
+ # Call AI model to generate title
354
+ try:
355
+ from langchain_groq import ChatGroq
356
+ from langchain.prompts import ChatPromptTemplate
357
+ import json
358
+
359
+ # Use a lightweight model for title generation (like Haiku in Claude Code)
360
+ llm = ChatGroq(
361
+ api_key=GROK_API_KEY,
362
+ model="mixtral-8x7b-32768", # Fast and cheap
363
+ temperature=0.3 # Low temperature for consistency
364
+ )
365
+
366
+ # Professional prompt design (inspired by Claude Code)
367
+ prompt = ChatPromptTemplate.from_messages([
368
+ ("system", """Generate a concise, sentence-case title (3-7 words or 10-20 Chinese characters) that captures the main topic or goal of this conversation.
369
+
370
+ Requirements:
371
+ 1. Be specific and descriptive
372
+ 2. Use sentence case (capitalize only first word and proper nouns)
373
+ 3. No quotes, colons, or punctuation in the title
374
+ 4. Avoid vague titles like "Chat" or "Question"
375
+ 5. Return ONLY the title text, no JSON, no explanations
376
+
377
+ Good examples:
378
+ - "Fix login button on mobile"
379
+ - "Add OAuth authentication"
380
+ - "推荐性价比高的笔记本电脑"
381
+ - "查询北京明天天气"
382
+
383
+ Bad examples:
384
+ - "Code changes" (too vague)
385
+ - "Investigate and fix the issue where..." (too long)
386
+ - "Fix Login Button On Mobile" (wrong case)
387
+ - "Chat about coding" (vague)"""),
388
+ ("human", "Generate a title for this conversation:\n\n{message}")
389
+ ])
390
+
391
+ chain = prompt | llm
392
+ response = chain.invoke({"message": request.message})
393
+
394
+ # Extract and clean title
395
+ title = response.content.strip()
396
+
397
+ # Remove quotes and common prefixes
398
+ title = title.strip('"\'`。.')
399
+ if title.lower().startswith('title:'):
400
+ title = title[6:].strip()
401
+
402
+ # Validate title
403
+ if not title or len(title) < 2:
404
+ raise ValueError("Title too short")
405
+
406
+ print(f"[Title API] Successfully generated: '{title}'")
407
+
408
+ return {
409
+ "success": True,
410
+ "title": title,
411
+ "method": "ai"
412
+ }
413
+
414
+ except Exception as ai_error:
415
+ print(f"[Title API] AI generation failed: {ai_error}")
416
+ # Fallback to rule-based generation
417
+ fallback_title = generate_fallback_title(request.message)
418
+ return {
419
+ "success": False,
420
+ "title": fallback_title,
421
+ "method": "fallback",
422
+ "error": str(ai_error)
423
+ }
424
+
425
+ except Exception as e:
426
+ print(f"[Title API] Unexpected error: {e}")
427
+ return {
428
+ "success": False,
429
+ "title": generate_fallback_title(request.message),
430
+ "method": "fallback",
431
+ "error": str(e)
432
+ }
433
+
434
+ def generate_fallback_title(message: str) -> str:
435
+ """
436
+ 备用标题生成方案(当 API 不可用时)
437
+ 基于关键词匹配和文本提取
438
+ """
439
+ if not message or message.strip() == '':
440
+ return 'New conversation'
441
+
442
+ message_lower = message.lower()
443
+
444
+ # 基于关键词的语义匹配
445
+ topic_keywords = {
446
+ 'Shopping': ['buy', 'product', 'shop', 'recommend', 'price', 'cheap', 'expensive', 'purchase', 'amazon', '商品', '购物', '买', '推荐'],
447
+ 'Food Order': ['food', 'eat', 'restaurant', 'order', 'delivery', 'hungry', 'meal', 'lunch', 'dinner', '饿了', '吃', '餐厅', '外卖'],
448
+ 'Travel Planning': ['travel', 'flight', 'trip', 'hotel', 'vacation', 'book', 'ticket', 'airport', '旅行', '机票', '酒店'],
449
+ 'Ride Booking': ['taxi', 'ride', 'uber', 'grab', 'car', 'drive', 'pickup', '打车', '叫车'],
450
+ 'Weather': ['weather', 'temperature', 'rain', 'sunny', 'forecast', 'cold', 'hot', '天气', '下雨', '温度'],
451
+ 'Schedule': ['meeting', 'schedule', 'calendar', 'event', 'appointment', 'reminder', '会议', '日程', '提醒', '安排'],
452
+ 'Tech': ['computer', 'laptop', 'phone', 'tech', 'electronic', 'device', 'cpu', 'gpu', '电脑', '手机', '电子'],
453
+ 'Fashion': ['clothes', 'shirt', 'dress', 'shoes', 'wear', 'fashion', 'style', '衣服', '时尚', '鞋子'],
454
+ 'Health': ['health', 'exercise', 'gym', 'workout', 'diet', 'fitness', 'medicine', '健康', '运动', '健身'],
455
+ 'Entertainment': ['movie', 'music', 'game', 'book', 'show', 'play', 'watch', '电影', '音乐', '游戏', '书']
456
+ }
457
+
458
+ # 检测主题
459
+ for topic, keywords in topic_keywords.items():
460
+ if any(keyword in message_lower for keyword in keywords):
461
+ return topic
462
+
463
+ # 提取第一个完整子句(到逗号或标点)
464
+ first_clause = message.split(r'[,.;:!?]')[0].strip()
465
+ if first_clause and len(first_clause) <= 30:
466
+ return first_clause
467
+
468
+ # 截取前 30 个字符
469
+ return message[:30] + ('...' if len(message) > 30 else '')
470
+
471
+ # ============================================
472
+ # Virtual Avatar System API Endpoints
473
+ # ============================================
474
+
475
+ class CreateAvatarRequest(BaseModel):
476
+ name: str
477
+ description: Optional[str] = ""
478
+ personality_traits: Optional[List[str]] = None
479
+ current_age: Optional[int] = 25
480
+ creator_id: Optional[str] = None
481
+
482
+ class UpdateAvatarRequest(BaseModel):
483
+ name: Optional[str] = None
484
+ description: Optional[str] = None
485
+ personality_traits: Optional[List[str]] = None
486
+ current_age: Optional[int] = None
487
+ avatar_image: Optional[str] = None
488
+
489
+ class SetAgeRequest(BaseModel):
490
+ age: int
491
+
492
+ class AddMemoryRequest(BaseModel):
493
+ content: str
494
+ min_age: Optional[int] = 0
495
+ max_age: Optional[int] = 150
496
+ clarity: Optional[int] = 80
497
+ emotional_tone: Optional[str] = "neutral"
498
+ topic: Optional[str] = None
499
+
500
+ class ExportAvatarRequest(BaseModel):
501
+ export_format: Optional[str] = "avatar"
502
+
503
+ class ListForSaleRequest(BaseModel):
504
+ price: float
505
+ description: Optional[str] = None
506
+
507
+ class PurchaseAvatarRequest(BaseModel):
508
+ buyer_id: str
509
+
510
+ @app.get("/api/avatar")
511
+ async def list_avatars(creator_id: Optional[str] = None):
512
+ """List all avatars, optionally filtered by creator"""
513
+ avatars = avatar_manager.list_avatars(creator_id)
514
+ return {"success": True, "avatars": avatars}
515
+
516
+ @app.post("/api/avatar")
517
+ async def create_avatar(request: CreateAvatarRequest):
518
+ """Create a new virtual avatar"""
519
+ try:
520
+ avatar = avatar_manager.create_avatar(
521
+ name=request.name,
522
+ description=request.description,
523
+ personality_traits=request.personality_traits,
524
+ current_age=request.current_age,
525
+ creator_id=request.creator_id
526
+ )
527
+ return {"success": True, "avatar": avatar.to_dict()}
528
+ except Exception as e:
529
+ raise HTTPException(status_code=500, detail=str(e))
530
+
531
+ @app.get("/api/avatar/{avatar_id}")
532
+ async def get_avatar(avatar_id: str):
533
+ """Get a specific avatar by ID"""
534
+ avatar = avatar_manager.load_avatar(avatar_id)
535
+ if avatar:
536
+ return {"success": True, "avatar": avatar.to_dict()}
537
+ raise HTTPException(status_code=404, detail="Avatar not found")
538
+
539
+ @app.put("/api/avatar/{avatar_id}")
540
+ async def update_avatar(avatar_id: str, request: UpdateAvatarRequest):
541
+ """Update avatar information"""
542
+ update_data = {k: v for k, v in request.dict().items() if v is not None}
543
+ avatar = avatar_manager.update_avatar(avatar_id, **update_data)
544
+ if avatar:
545
+ return {"success": True, "avatar": avatar.to_dict()}
546
+ raise HTTPException(status_code=404, detail="Avatar not found")
547
+
548
+ @app.delete("/api/avatar/{avatar_id}")
549
+ async def delete_avatar(avatar_id: str):
550
+ """Delete an avatar"""
551
+ deleted = avatar_manager.delete_avatar(avatar_id)
552
+ if deleted:
553
+ return {"success": True, "message": "Avatar deleted"}
554
+ raise HTTPException(status_code=404, detail="Avatar not found")
555
+
556
+ @app.post("/api/avatar/{avatar_id}/age")
557
+ async def set_avatar_age(avatar_id: str, request: SetAgeRequest):
558
+ """Set avatar's current age"""
559
+ avatar = avatar_manager.set_age(avatar_id, request.age)
560
+ if avatar:
561
+ return {"success": True, "avatar": avatar.to_dict()}
562
+ raise HTTPException(status_code=404, detail="Avatar not found")
563
+
564
+ @app.get("/api/avatar/{avatar_id}/memories")
565
+ async def get_avatar_memories(avatar_id: str, age: Optional[int] = None):
566
+ """Get avatar memories, optionally filtered by age"""
567
+ if age is not None:
568
+ memories = avatar_manager.get_memories_for_age(avatar_id, age)
569
+ else:
570
+ memories = tree_memory_system.get_all_memories(avatar_id)
571
+ return {"success": True, "memories": memories, "filter_age": age}
572
+
573
+ @app.post("/api/avatar/{avatar_id}/memories")
574
+ async def add_memory(avatar_id: str, request: AddMemoryRequest):
575
+ """Add a new memory to avatar"""
576
+ try:
577
+ memory_id = avatar_manager.add_memory(
578
+ avatar_id=avatar_id,
579
+ content=request.content,
580
+ min_age=request.min_age,
581
+ max_age=request.max_age,
582
+ clarity=request.clarity,
583
+ emotional_tone=request.emotional_tone,
584
+ topic=request.topic
585
+ )
586
+ return {"success": True, "memory_id": memory_id}
587
+ except Exception as e:
588
+ raise HTTPException(status_code=500, detail=str(e))
589
+
590
+ @app.delete("/api/avatar/{avatar_id}/memories/{memory_id}")
591
+ async def delete_memory(avatar_id: str, memory_id: str):
592
+ """Delete a memory"""
593
+ deleted = tree_memory_system.delete_memory(avatar_id, memory_id)
594
+ if deleted:
595
+ return {"success": True, "message": "Memory deleted"}
596
+ raise HTTPException(status_code=404, detail="Memory not found")
597
+
598
+ @app.get("/api/avatar/{avatar_id}/tree")
599
+ async def get_memory_tree(avatar_id: str):
600
+ """Get the inverted tree memory structure"""
601
+ tree = avatar_manager.get_tree_structure(avatar_id)
602
+ if tree:
603
+ return {"success": True, "tree": tree}
604
+ raise HTTPException(status_code=404, detail="Tree not found")
605
+
606
+ class TreeMemorySearchRequest(BaseModel):
607
+ query: str
608
+ min_relevance: Optional[float] = 0.3
609
+ max_results: Optional[int] = 10
610
+
611
+ @app.post("/api/avatar/{avatar_id}/memories/search")
612
+ async def search_tree_memories(avatar_id: str, request: TreeMemorySearchRequest):
613
+ """Search tree memories by relevance"""
614
+ memories = tree_memory_system.search_relevant_memories(
615
+ avatar_id,
616
+ request.query,
617
+ request.min_relevance,
618
+ request.max_results
619
+ )
620
+ return {"success": True, "memories": memories}
621
+
622
+ @app.get("/api/avatar/{avatar_id}/memories/context")
623
+ async def get_tree_context(avatar_id: str, query: str, min_relevance: Optional[float] = 0.3, max_results: Optional[int] = 5):
624
+ """Get formatted context from tree memories"""
625
+ context = tree_memory_system.get_relevant_context(
626
+ avatar_id,
627
+ query,
628
+ min_relevance,
629
+ max_results
630
+ )
631
+ return {"success": True, "context": context}
632
+
633
+ @app.post("/api/avatar/{avatar_id}/developer-mode")
634
+ async def toggle_developer_mode(avatar_id: str):
635
+ """Toggle developer mode for avatar"""
636
+ enabled = avatar_manager.toggle_developer_mode(avatar_id)
637
+ if enabled is not None:
638
+ return {"success": True, "developer_mode": enabled}
639
+ raise HTTPException(status_code=404, detail="Avatar not found")
640
+
641
+ @app.post("/api/avatar/{avatar_id}/export")
642
+ async def export_avatar(avatar_id: str, request: ExportAvatarRequest):
643
+ """Export avatar to file"""
644
+ filepath = avatar_packer.export_avatar(avatar_id, request.export_format)
645
+ if filepath:
646
+ return {"success": True, "filepath": filepath, "filename": os.path.basename(filepath)}
647
+ raise HTTPException(status_code=404, detail="Avatar not found")
648
+
649
+ @app.get("/api/exports")
650
+ async def list_exports():
651
+ """List all exported files"""
652
+ exports = avatar_packer.list_exports()
653
+ return {"success": True, "exports": exports}
654
+
655
+ @app.delete("/api/exports/{filename}")
656
+ async def delete_export(filename: str):
657
+ """Delete an exported file"""
658
+ deleted = avatar_packer.delete_export(filename)
659
+ if deleted:
660
+ return {"success": True, "message": "Export deleted"}
661
+ raise HTTPException(status_code=404, detail="Export not found")
662
+
663
+ @app.post("/api/avatar/{avatar_id}/list-for-sale")
664
+ async def list_for_sale(avatar_id: str, request: ListForSaleRequest):
665
+ """List avatar for sale on marketplace"""
666
+ success = marketplace.list_for_sale(avatar_id, request.price, request.description)
667
+ if success:
668
+ return {"success": True, "message": "Avatar listed for sale"}
669
+ raise HTTPException(status_code=404, detail="Avatar not found")
670
+
671
+ @app.post("/api/avatar/{avatar_id}/remove-from-sale")
672
+ async def remove_from_sale(avatar_id: str):
673
+ """Remove avatar from marketplace"""
674
+ success = marketplace.remove_from_sale(avatar_id)
675
+ if success:
676
+ return {"success": True, "message": "Avatar removed from sale"}
677
+ raise HTTPException(status_code=404, detail="Avatar not found")
678
+
679
+ @app.get("/api/marketplace")
680
+ async def get_marketplace():
681
+ """Get all avatars listed for sale"""
682
+ listings = marketplace.get_market_listings()
683
+ return {"success": True, "listings": listings}
684
+
685
+ @app.post("/api/marketplace/{avatar_id}/purchase")
686
+ async def purchase_avatar(avatar_id: str, request: PurchaseAvatarRequest):
687
+ """Purchase an avatar from marketplace"""
688
+ new_avatar_id = marketplace.purchase_avatar(avatar_id, request.buyer_id)
689
+ if new_avatar_id:
690
+ return {"success": True, "new_avatar_id": new_avatar_id, "message": "Avatar purchased successfully"}
691
+ raise HTTPException(status_code=404, detail="Avatar not available for purchase")
692
+
693
+ class SelectAvatarRequest(BaseModel):
694
+ user_id: str
695
+ avatar_id: str
696
+
697
+ @app.post("/api/user/select-avatar")
698
+ async def select_user_avatar(request: SelectAvatarRequest):
699
+ """Select an avatar for the user"""
700
+ try:
701
+ user_settings_file = os.path.join("user_data", f"{request.user_id}_settings.json")
702
+ os.makedirs("user_data", exist_ok=True)
703
+
704
+ settings = {}
705
+ if os.path.exists(user_settings_file):
706
+ with open(user_settings_file, "r", encoding="utf-8") as f:
707
+ settings = json.load(f)
708
+
709
+ settings["selected_avatar_id"] = request.avatar_id
710
+ settings["selected_at"] = datetime.now().isoformat()
711
+
712
+ with open(user_settings_file, "w", encoding="utf-8") as f:
713
+ json.dump(settings, f, ensure_ascii=False, indent=2)
714
+
715
+ return {"success": True, "selected_avatar_id": request.avatar_id}
716
+ except Exception as e:
717
+ raise HTTPException(status_code=500, detail=str(e))
718
+
719
+ @app.get("/api/user/selected-avatar")
720
+ async def get_selected_avatar(user_id: str):
721
+ """Get user's selected avatar"""
722
+ try:
723
+ user_settings_file = os.path.join("user_data", f"{user_id}_settings.json")
724
+
725
+ if os.path.exists(user_settings_file):
726
+ with open(user_settings_file, "r", encoding="utf-8") as f:
727
+ settings = json.load(f)
728
+ avatar_id = settings.get("selected_avatar_id")
729
+ if avatar_id:
730
+ avatar = avatar_manager.load_avatar(avatar_id)
731
+ if avatar:
732
+ return {"success": True, "avatar": avatar.to_dict()}
733
+
734
+ return {"success": True, "avatar": None}
735
+ except Exception as e:
736
+ raise HTTPException(status_code=500, detail=str(e))
737
+
738
+ @app.get("/api/user/my-avatars")
739
+ async def get_user_avatars(user_id: str):
740
+ """Get all avatars owned by user"""
741
+ try:
742
+ avatars = avatar_manager.list_avatars(user_id)
743
+ return {"success": True, "avatars": avatars}
744
+ except Exception as e:
745
+ raise HTTPException(status_code=500, detail=str(e))
746
+
747
+ @app.get("/avatar-manager")
748
+ async def avatar_manager():
749
+ """Virtual Avatar Manager Page"""
750
+ manager_path = os.path.join(os.path.dirname(__file__), "static", "avatar-manager.html")
751
+ if os.path.exists(manager_path):
752
+ return FileResponse(manager_path)
753
+ raise HTTPException(status_code=404, detail="Page not found")
754
+
755
+ @app.get("/design-template")
756
+ async def design_template():
757
+ """Design Template Preview Page"""
758
+ template_path = os.path.join(os.path.dirname(__file__), "static", "design-template.html")
759
+ if os.path.exists(template_path):
760
+ return FileResponse(template_path)
761
+ raise HTTPException(status_code=404, detail="Design template page not found")
762
+
763
+ @app.get("/design-template-roblox")
764
+ async def design_template_roblox():
765
+ """Roblox Style Design Template Preview Page"""
766
+ template_path = os.path.join(os.path.dirname(__file__), "static", "design-template-roblox.html")
767
+ if os.path.exists(template_path):
768
+ return FileResponse(template_path)
769
+ raise HTTPException(status_code=404, detail="Roblox design template page not found")
770
+
771
+ @app.get("/roblox-interface")
772
+ async def roblox_interface():
773
+ """Full Roblox Style Interface"""
774
+ interface_path = os.path.join(os.path.dirname(__file__), "static", "roblox-interface.html")
775
+ if os.path.exists(interface_path):
776
+ return FileResponse(interface_path)
777
+ raise HTTPException(status_code=404, detail="Roblox interface page not found")
778
+
779
+ @app.get("/grok-roblox-combo")
780
+ async def grok_roblox_combo():
781
+ """Grok Layout + Roblox Style Interface"""
782
+ interface_path = os.path.join(os.path.dirname(__file__), "static", "grok-roblox-combo.html")
783
+ if os.path.exists(interface_path):
784
+ return FileResponse(interface_path)
785
+ raise HTTPException(status_code=404, detail="Grok-Roblox combo page not found")
786
+
787
+ @app.get("/template-main")
788
+ async def template_main():
789
+ """Template - Main Chat Interface"""
790
+ template_path = os.path.join(os.path.dirname(__file__), "static", "template-main.html")
791
+ if os.path.exists(template_path):
792
+ return FileResponse(template_path)
793
+ raise HTTPException(status_code=404, detail="Template main page not found")
794
+
795
+ @app.get("/template-profile")
796
+ async def template_profile():
797
+ """Template - Profile Settings Page"""
798
+ template_path = os.path.join(os.path.dirname(__file__), "static", "template-profile.html")
799
+ if os.path.exists(template_path):
800
+ return FileResponse(template_path)
801
+ raise HTTPException(status_code=404, detail="Template profile page not found")
802
+
803
+ @app.get("/template-roblox")
804
+ async def template_roblox():
805
+ """Template - Roblox Avatar Manager"""
806
+ template_path = os.path.join(os.path.dirname(__file__), "static", "template-roblox.html")
807
+ if os.path.exists(template_path):
808
+ return FileResponse(template_path)
809
+ raise HTTPException(status_code=404, detail="Template Roblox page not found")
810
+
811
+ @app.get("/avatar-manager-real")
812
+ async def avatar_manager_real():
813
+ """Real Avatar Manager with Import/Export"""
814
+ manager_path = os.path.join(os.path.dirname(__file__), "static", "avatar-manager-real.html")
815
+ if os.path.exists(manager_path):
816
+ return FileResponse(manager_path)
817
+ raise HTTPException(status_code=404, detail="Avatar manager page not found")
818
+
819
+ static_dir = os.path.join(os.path.dirname(__file__), "static")
820
+ if os.path.exists(static_dir):
821
+ app.mount("/static", StaticFiles(directory=static_dir), name="static")
822
+
823
+ if __name__ == "__main__":
824
+ import uvicorn
825
+ uvicorn.run(
826
+ "main:app",
827
+ host="0.0.0.0",
828
+ port=8000,
829
+ reload=DEBUG
830
+ )