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,449 @@
1
+ import json
2
+ import os
3
+ from typing import Dict, List, Any, Optional, Callable
4
+ from enum import Enum
5
+ from dataclasses import dataclass
6
+ import sys
7
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
8
+ from mock_shopping_data import search_mock_products, get_price_history, compare_prices
9
+
10
+ class ToolType(Enum):
11
+ VISUALIZATION = "visualization"
12
+ DATA_ANALYSIS = "data_analysis"
13
+ CALCULATION = "calculation"
14
+ GENERAL = "general"
15
+
16
+ @dataclass
17
+ class ToolParameter:
18
+ name: str
19
+ type: str
20
+ description: str
21
+ required: bool = True
22
+ default: Any = None
23
+
24
+ @dataclass
25
+ class ToolDefinition:
26
+ name: str
27
+ description: str
28
+ type: ToolType
29
+ parameters: List[ToolParameter]
30
+ handler: Callable
31
+
32
+ class MCPToolManager:
33
+ def __init__(self):
34
+ self.tools: Dict[str, ToolDefinition] = {}
35
+ self._register_builtin_tools()
36
+
37
+ def _register_builtin_tools(self):
38
+ self.register_tool(ToolDefinition(
39
+ name="create_chart",
40
+ description="Create a visualization chart (bar, line, pie, etc.)",
41
+ type=ToolType.VISUALIZATION,
42
+ parameters=[
43
+ ToolParameter("chart_type", "string", "Type of chart: bar, line, pie, scatter", True),
44
+ ToolParameter("title", "string", "Chart title", True),
45
+ ToolParameter("data", "object", "Chart data: {labels: [], values: []}", True),
46
+ ToolParameter("x_label", "string", "X-axis label", False),
47
+ ToolParameter("y_label", "string", "Y-axis label", False)
48
+ ],
49
+ handler=self._create_chart
50
+ ))
51
+
52
+ self.register_tool(ToolDefinition(
53
+ name="create_table",
54
+ description="Create a data table visualization",
55
+ type=ToolType.VISUALIZATION,
56
+ parameters=[
57
+ ToolParameter("title", "string", "Table title", True),
58
+ ToolParameter("columns", "array", "Column names: [col1, col2, ...]", True),
59
+ ToolParameter("rows", "array", "Table rows: [[row1val1, row1val2], ...]", True)
60
+ ],
61
+ handler=self._create_table
62
+ ))
63
+
64
+ self.register_tool(ToolDefinition(
65
+ name="calculate",
66
+ description="Perform calculations and show results",
67
+ type=ToolType.CALCULATION,
68
+ parameters=[
69
+ ToolParameter("expression", "string", "Mathematical expression", True),
70
+ ToolParameter("description", "string", "What this calculation is for", False)
71
+ ],
72
+ handler=self._calculate
73
+ ))
74
+
75
+ self.register_tool(ToolDefinition(
76
+ name="create_timeline",
77
+ description="Create a timeline visualization",
78
+ type=ToolType.VISUALIZATION,
79
+ parameters=[
80
+ ToolParameter("title", "string", "Timeline title", True),
81
+ ToolParameter("events", "array", "Events: [{date: '', title: '', description: ''}, ...]", True)
82
+ ],
83
+ handler=self._create_timeline
84
+ ))
85
+
86
+ self.register_tool(ToolDefinition(
87
+ name="search_products",
88
+ description="Search for products across multiple platforms",
89
+ type=ToolType.DATA_ANALYSIS,
90
+ parameters=[
91
+ ToolParameter("keyword", "string", "Product keyword to search", True)
92
+ ],
93
+ handler=self._search_products
94
+ ))
95
+
96
+ self.register_tool(ToolDefinition(
97
+ name="compare_prices",
98
+ description="Compare prices of a product across platforms",
99
+ type=ToolType.DATA_ANALYSIS,
100
+ parameters=[
101
+ ToolParameter("product_name", "string", "Name of the product", True)
102
+ ],
103
+ handler=self._compare_prices
104
+ ))
105
+
106
+ self.register_tool(ToolDefinition(
107
+ name="get_price_history",
108
+ description="Get price history of a product",
109
+ type=ToolType.VISUALIZATION,
110
+ parameters=[
111
+ ToolParameter("product_id", "string", "Product ID", True),
112
+ ToolParameter("days", "integer", "Number of days to show (default: 30)", False, 30)
113
+ ],
114
+ handler=self._get_price_history
115
+ ))
116
+
117
+ def register_tool(self, tool: ToolDefinition):
118
+ self.tools[tool.name] = tool
119
+
120
+ def get_tool(self, name: str) -> Optional[ToolDefinition]:
121
+ return self.tools.get(name)
122
+
123
+ def list_tools(self, tool_type: Optional[ToolType] = None) -> List[ToolDefinition]:
124
+ if tool_type:
125
+ return [t for t in self.tools.values() if t.type == tool_type]
126
+ return list(self.tools.values())
127
+
128
+ async def execute_tool(self, tool_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
129
+ tool = self.get_tool(tool_name)
130
+ if not tool:
131
+ return {"success": False, "error": f"Tool '{tool_name}' not found"}
132
+
133
+ try:
134
+ result = await tool.handler(**params)
135
+ return {"success": True, "result": result}
136
+ except Exception as e:
137
+ return {"success": False, "error": str(e)}
138
+
139
+ async def _create_chart(self, chart_type: str, title: str, data: Dict[str, Any],
140
+ x_label: Optional[str] = None, y_label: Optional[str] = None) -> Dict[str, Any]:
141
+ return {
142
+ "type": "chart",
143
+ "chart_type": chart_type,
144
+ "title": title,
145
+ "data": data,
146
+ "x_label": x_label,
147
+ "y_label": y_label,
148
+ "html": self._generate_chart_html(chart_type, title, data, x_label, y_label)
149
+ }
150
+
151
+ async def _create_table(self, title: str, columns: List[str], rows: List[List[Any]]) -> Dict[str, Any]:
152
+ return {
153
+ "type": "table",
154
+ "title": title,
155
+ "columns": columns,
156
+ "rows": rows,
157
+ "html": self._generate_table_html(title, columns, rows)
158
+ }
159
+
160
+ async def _calculate(self, expression: str, description: Optional[str] = None) -> Dict[str, Any]:
161
+ try:
162
+ result = eval(expression, {"__builtins__": {}}, {})
163
+ return {
164
+ "type": "calculation",
165
+ "expression": expression,
166
+ "description": description,
167
+ "result": result,
168
+ "html": self._generate_calculation_html(expression, result, description)
169
+ }
170
+ except Exception as e:
171
+ return {"type": "calculation", "expression": expression, "error": str(e)}
172
+
173
+ async def _create_timeline(self, title: str, events: List[Dict[str, Any]]) -> Dict[str, Any]:
174
+ return {
175
+ "type": "timeline",
176
+ "title": title,
177
+ "events": events,
178
+ "html": self._generate_timeline_html(title, events)
179
+ }
180
+
181
+ async def _search_products(self, keyword: str) -> Dict[str, Any]:
182
+ results = search_mock_products(keyword)
183
+ return {
184
+ "type": "product_list",
185
+ "keyword": keyword,
186
+ "results": results["results"],
187
+ "html": self._generate_product_list_html(keyword, results["results"])
188
+ }
189
+
190
+ async def _compare_prices(self, product_name: str) -> Dict[str, Any]:
191
+ comparison = compare_prices(product_name)
192
+ return {
193
+ "type": "price_comparison",
194
+ "product_name": product_name,
195
+ "comparison": comparison,
196
+ "html": self._generate_price_comparison_html(product_name, comparison)
197
+ }
198
+
199
+ async def _get_price_history(self, product_id: str, days: int = 30) -> Dict[str, Any]:
200
+ history = get_price_history(product_id, days)
201
+ return {
202
+ "type": "price_history",
203
+ "product_id": product_id,
204
+ "history": history["history"],
205
+ "html": self._generate_price_history_html(product_id, history["history"])
206
+ }
207
+
208
+ def _generate_chart_html(self, chart_type: str, title: str, data: Dict[str, Any],
209
+ x_label: Optional[str], y_label: Optional[str]) -> str:
210
+ labels = data.get("labels", [])
211
+ values = data.get("values", [])
212
+
213
+ return f"""
214
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; border-radius: 16px; color: white; margin: 16px 0;">
215
+ <h3 style="margin: 0 0 16px 0; font-size: 20px;">{title}</h3>
216
+ <div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px;">
217
+ <span style="background: rgba(255,255,255,0.2); padding: 4px 12px; border-radius: 20px; font-size: 14px;">
218
+ {chart_type.capitalize()} Chart
219
+ </span>
220
+ </div>
221
+ <div style="background: rgba(0,0,0,0.2); padding: 16px; border-radius: 12px;">
222
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
223
+ <span>{x_label or 'Labels'}</span>
224
+ <span>{y_label or 'Values'}</span>
225
+ </div>
226
+ {''.join([f'''
227
+ <div style="display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.2);">
228
+ <span>{labels[i] if i < len(labels) else '-'}</span>
229
+ <span style="font-weight: 600;">{values[i] if i < len(values) else '-'}</span>
230
+ </div>
231
+ ''' for i in range(max(len(labels), len(values)))])}
232
+ </div>
233
+ </div>
234
+ """
235
+
236
+ def _generate_table_html(self, title: str, columns: List[str], rows: List[List[Any]]) -> str:
237
+ return f"""
238
+ <div style="background: white; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.1); margin: 16px 0;">
239
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 16px 24px; color: white;">
240
+ <h3 style="margin: 0; font-size: 18px;">{title}</h3>
241
+ </div>
242
+ <table style="width: 100%; border-collapse: collapse;">
243
+ <thead>
244
+ <tr style="background: #f8f9fa;">
245
+ {''.join([f'<th style="padding: 12px 16px; text-align: left; border-bottom: 2px solid #e9ecef;">{col}</th>' for col in columns])}
246
+ </tr>
247
+ </thead>
248
+ <tbody>
249
+ {''.join([f'''
250
+ <tr style="border-bottom: 1px solid #f1f3f4;">
251
+ {''.join([f'<td style="padding: 12px 16px;">{cell}</td>' for cell in row])}
252
+ </tr>
253
+ ''' for row in rows])}
254
+ </tbody>
255
+ </table>
256
+ </div>
257
+ """
258
+
259
+ def _generate_calculation_html(self, expression: str, result: Any, description: Optional[str]) -> str:
260
+ return f"""
261
+ <div style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); padding: 24px; border-radius: 16px; color: white; margin: 16px 0;">
262
+ {f'<p style="margin: 0 0 12px 0; opacity: 0.9;">{description}</p>' if description else ''}
263
+ <div style="display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;">
264
+ <div style="background: rgba(0,0,0,0.2); padding: 12px 20px; border-radius: 12px; font-family: monospace; font-size: 18px;">
265
+ {expression}
266
+ </div>
267
+ <span style="font-size: 24px;">=</span>
268
+ <div style="background: rgba(255,255,255,0.2); padding: 12px 20px; border-radius: 12px; font-size: 24px; font-weight: 700;">
269
+ {result}
270
+ </div>
271
+ </div>
272
+ </div>
273
+ """
274
+
275
+ def _generate_timeline_html(self, title: str, events: List[Dict[str, Any]]) -> str:
276
+ return f"""
277
+ <div style="background: white; padding: 24px; border-radius: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); margin: 16px 0;">
278
+ <h3 style="margin: 0 0 24px 0; font-size: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
279
+ {title}
280
+ </h3>
281
+ <div style="position: relative; padding-left: 24px;">
282
+ <div style="position: absolute; left: 7px; top: 0; bottom: 0; width: 2px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);"></div>
283
+ {''.join([f'''
284
+ <div style="margin-bottom: 24px; position: relative;">
285
+ <div style="position: absolute; left: -28px; top: 4px; width: 16px; height: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; border: 3px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.2);"></div>
286
+ <div style="background: #f8f9fa; padding: 16px; border-radius: 12px; border-left: 4px solid #667eea;">
287
+ <div style="font-weight: 600; color: #667eea; margin-bottom: 4px;">{event.get('date', '')}</div>
288
+ <div style="font-weight: 600; margin-bottom: 4px;">{event.get('title', '')}</div>
289
+ <div style="color: #6e6e73;">{event.get('description', '')}</div>
290
+ </div>
291
+ </div>
292
+ ''' for event in events])}
293
+ </div>
294
+ </div>
295
+ """
296
+
297
+ def _generate_product_list_html(self, keyword: str, products: List[Dict[str, Any]]) -> str:
298
+ product_cards = ''.join([self._generate_product_card(product) for product in products])
299
+ return f"""
300
+ <div style="margin: 16px 0;">
301
+ <h3 style="margin: 0 0 16px 0; font-size: 18px; color: #1d1d1f;">
302
+ 🔍 Results for "{keyword}"
303
+ </h3>
304
+ <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;">
305
+ {product_cards}
306
+ </div>
307
+ </div>
308
+ """
309
+
310
+ def _generate_product_card(self, product: Dict[str, Any]) -> str:
311
+ price_change_icon = "⬇️" if product.get("priceChangeDirection") == "down" else "⬆️"
312
+ price_change_class = "down" if product.get("priceChangeDirection") == "down" else "up"
313
+
314
+ return f"""
315
+ <a href="{product['url']}" target="_blank" style="text-decoration: none; color: inherit;">
316
+ <div style="background: white; border-radius: 16px; overflow: hidden; box-shadow: 0 2px 12px rgba(0,0,0,0.08); transition: all 0.3s ease; cursor: pointer; border: 1px solid #e6e6e6;" onmouseover="this.style.transform='translateY(-4px)';this.style.boxShadow='0 8px 24px rgba(0,0,0,0.12)';" onmouseout="this.style.transform='translateY(0)';this.style.boxShadow='0 2px 12px rgba(0,0,0,0.08)';">
317
+ <div style="padding: 16px; display: flex; gap: 16px; align-items: flex-start;">
318
+ <img src="{product['image']}" alt="{product['title']}" style="width: 88px; height: 88px; object-fit: cover; border-radius: 12px; background: #f5f5f7; flex-shrink: 0;">
319
+ <div style="flex: 1; min-width: 0;">
320
+ <div style="font-weight: 600; font-size: 15px; color: #1d1d1f; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">
321
+ {product['title']}
322
+ </div>
323
+ <div style="font-size: 13px; color: #6e6e73; margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">
324
+ {product.get('description', '')}
325
+ </div>
326
+ <div style="display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px;">
327
+ <span style="font-size: 20px; font-weight: 700; color: #1d1d1f;">
328
+ ${product['price']}
329
+ </span>
330
+ {f'<span style="font-size: 14px; color: #86868b; text-decoration: line-through;">${product["originalPrice"]}</span>' if product.get('originalPrice') else ''}
331
+ </div>
332
+ <div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
333
+ <span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 20px; font-size: 12px; font-weight: 500;">
334
+ {product['platform']}
335
+ </span>
336
+ {f'''
337
+ <span style="display: flex; align-items: center; gap: 4px; font-size: 14px; color: #ff3b30;">
338
+ <i class="fas fa-star"></i>
339
+ {product['rating']} ({product['reviewCount']} reviews)
340
+ </span>
341
+ ''' if product.get('rating') else ''}
342
+ {f'''
343
+ <span style="color: {'#34c759' if price_change_class == 'down' else '#ff3b30'}; font-size: 14px; font-weight: 500;">
344
+ {price_change_icon} {abs(product.get('priceChange', 0))}%
345
+ </span>
346
+ ''' if product.get('priceChange') else ''}
347
+ </div>
348
+ </div>
349
+ </div>
350
+ <div style="background: #f5f5f7; padding: 12px 16px; display: flex; justify-content: flex-end; gap: 8px;">
351
+ <span style="color: #007aff; font-size: 14px; font-weight: 500;">
352
+ View Deal <i class="fas fa-external-link-alt" style="margin-left: 4px; font-size: 12px;"></i>
353
+ </span>
354
+ </div>
355
+ </div>
356
+ </a>
357
+ """
358
+
359
+ def _generate_price_comparison_html(self, product_name: str, comparison: Dict[str, Any]) -> str:
360
+ prices = comparison.get('prices', [])
361
+ lowest = comparison.get('lowest_price', {})
362
+
363
+ return f"""
364
+ <div style="background: white; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.1); margin: 16px 0;">
365
+ <div style="background: linear-gradient(135deg, #ff9500 0%, #ff6d00 100%); padding: 20px 24px; color: white;">
366
+ <h3 style="margin: 0 0 8px 0; font-size: 18px;">
367
+ 💸 Price Comparison
368
+ </h3>
369
+ <p style="margin: 0; opacity: 0.9; font-size: 14px;">
370
+ {product_name}
371
+ </p>
372
+ </div>
373
+ <div style="padding: 20px 24px;">
374
+ <div style="display: flex; flex-direction: column; gap: 12px;">
375
+ {''.join([f'''
376
+ <a href="{p['url']}" target="_blank" style="text-decoration: none; color: inherit;">
377
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: 16px; border-radius: 12px; background: {'#f0fff4' if p == lowest else '#f5f5f7'}; border: 2px solid {'#34c759' if p == lowest else 'transparent'};">
378
+ <div style="display: flex; align-items: center; gap: 12px;">
379
+ <span style="font-weight: 600; font-size: 16px;">{p['platform']}</span>
380
+ {f'<span style="background: #34c759; color: white; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">LOWEST</span>' if p == lowest else ''}
381
+ </div>
382
+ <span style="font-size: 20px; font-weight: 700; color: {'#34c759' if p == lowest else '#1d1d1f'};">
383
+ ${p['price']}
384
+ </span>
385
+ </div>
386
+ </a>
387
+ ''' for p in prices])}
388
+ </div>
389
+ {f'''
390
+ <div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid #e6e6e6;">
391
+ <div style="display: flex; justify-content: space-between; align-items: center;">
392
+ <span style="color: #6e6e73;">Maximum Savings</span>
393
+ <span style="font-size: 24px; font-weight: 700; color: #34c759;">
394
+ Save ${comparison.get('savings', 0)}
395
+ </span>
396
+ </div>
397
+ </div>
398
+ ''' if comparison.get('savings') else ''}
399
+ </div>
400
+ </div>
401
+ """
402
+
403
+ def _generate_price_history_html(self, product_id: str, history: List[Dict[str, Any]]) -> str:
404
+ labels = [item['date'] for item in history]
405
+ prices = [item['price'] for item in history]
406
+ min_price = min(prices) if prices else 0
407
+ max_price = max(prices) if prices else 1
408
+
409
+ return f"""
410
+ <div style="background: white; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.1); margin: 16px 0;">
411
+ <div style="background: linear-gradient(135deg, #5856d6 0%, #af52de 100%); padding: 20px 24px; color: white;">
412
+ <h3 style="margin: 0 0 8px 0; font-size: 18px;">
413
+ 📊 Price History
414
+ </h3>
415
+ <p style="margin: 0; opacity: 0.9; font-size: 14px;">
416
+ Last {len(history)} days
417
+ </p>
418
+ </div>
419
+ <div style="padding: 24px;">
420
+ <div style="display: flex; justify-content: space-between; margin-bottom: 16px; padding: 12px; background: #f5f5f7; border-radius: 12px;">
421
+ <div>
422
+ <div style="font-size: 12px; color: #6e6e73; margin-bottom: 4px;">Lowest</div>
423
+ <div style="font-size: 18px; font-weight: 700; color: #34c759;">${min_price}</div>
424
+ </div>
425
+ <div>
426
+ <div style="font-size: 12px; color: #6e6e73; margin-bottom: 4px;">Highest</div>
427
+ <div style="font-size: 18px; font-weight: 700; color: #ff3b30;">${max_price}</div>
428
+ </div>
429
+ <div>
430
+ <div style="font-size: 12px; color: #6e6e73; margin-bottom: 4px;">Current</div>
431
+ <div style="font-size: 18px; font-weight: 700; color: #007aff;">${prices[-1] if prices else 0}</div>
432
+ </div>
433
+ </div>
434
+ <div style="height: 120px; display: flex; align-items: flex-end; gap: 4px; padding: 0 8px;">
435
+ {''.join([f'''
436
+ <div style="flex: 1; display: flex; flex-direction: column; align-items: center;">
437
+ <div style="width: 100%; background: linear-gradient(180deg, #5856d6 0%, #af52de 100%); border-radius: 4px 4px 0 0; height: {((p - min_price) / (max_price - min_price) * 100) if max_price > min_price else 50}%; min-height: 8px;"></div>
438
+ <div style="font-size: 9px; color: #86868b; margin-top: 4px; writing-mode: vertical-rl; transform: rotate(180deg);">
439
+ {d.split('-')[2]}
440
+ </div>
441
+ </div>
442
+ ''' for d, p in zip(labels[-15:], prices[-15:])])}
443
+ </div>
444
+ </div>
445
+ </div>
446
+ """
447
+
448
+
449
+ mcp_tool_manager = MCPToolManager()
@@ -0,0 +1,92 @@
1
+ import json
2
+ import os
3
+ from typing import List, Dict, Any
4
+ from datetime import datetime
5
+
6
+ class MemorySystem:
7
+ def __init__(self, memory_dir: str = "user_memory"):
8
+ self.memory_dir = memory_dir
9
+ if not os.path.exists(memory_dir):
10
+ os.makedirs(memory_dir)
11
+
12
+ def _get_user_file(self, user_id: str) -> str:
13
+ return os.path.join(self.memory_dir, f"{user_id}.json")
14
+
15
+ def _load_user_memory(self, user_id: str) -> Dict[str, Any]:
16
+ file_path = self._get_user_file(user_id)
17
+ if os.path.exists(file_path):
18
+ with open(file_path, "r", encoding="utf-8") as f:
19
+ return json.load(f)
20
+ return {
21
+ "preferences": {},
22
+ "history": [],
23
+ "created_at": datetime.now().isoformat()
24
+ }
25
+
26
+ def _save_user_memory(self, user_id: str, memory: Dict[str, Any]):
27
+ file_path = self._get_user_file(user_id)
28
+ with open(file_path, "w", encoding="utf-8") as f:
29
+ json.dump(memory, f, ensure_ascii=False, indent=2)
30
+
31
+ def add_preference(self, user_id: str, key: str, value: str):
32
+ memory = self._load_user_memory(user_id)
33
+ memory["preferences"][key] = {
34
+ "value": value,
35
+ "updated_at": datetime.now().isoformat()
36
+ }
37
+ self._save_user_memory(user_id, memory)
38
+
39
+ def get_preferences(self, user_id: str) -> Dict[str, Any]:
40
+ memory = self._load_user_memory(user_id)
41
+ return memory.get("preferences", {})
42
+
43
+ def add_history(self, user_id: str, message: str, response: str, intent: str = None):
44
+ memory = self._load_user_memory(user_id)
45
+ memory["history"].append({
46
+ "message": message,
47
+ "response": response,
48
+ "intent": intent,
49
+ "timestamp": datetime.now().isoformat()
50
+ })
51
+ if len(memory["history"]) > 50:
52
+ memory["history"] = memory["history"][-50:]
53
+ self._save_user_memory(user_id, memory)
54
+
55
+ def get_relevant_memory(self, user_id: str, current_message: str) -> str:
56
+ memory = self._load_user_memory(user_id)
57
+ preferences = memory.get("preferences", {})
58
+
59
+ memory_text = ""
60
+
61
+ if preferences:
62
+ memory_text += "【用户偏好】\n"
63
+ for key, pref in preferences.items():
64
+ memory_text += f"- {key}: {pref['value']}\n"
65
+
66
+ recent_history = memory.get("history", [])[-5:]
67
+ if recent_history:
68
+ memory_text += "\n【最近对话】\n"
69
+ for item in recent_history:
70
+ memory_text += f"- 用户: {item['message']}\n"
71
+ memory_text += f"- 助手: {item['response'][:100]}...\n"
72
+
73
+ return memory_text if memory_text else "暂无用户记忆"
74
+
75
+ def extract_and_save_preferences(self, user_id: str, message: str, response: str):
76
+ message_lower = message.lower()
77
+
78
+ preference_keywords = {
79
+ "喜欢的品牌": ["喜欢", "偏好", "最爱", "常用"],
80
+ "不喜欢的东西": ["不喜欢", "讨厌", "不要"],
81
+ "饮食偏好": ["吃素", "不吃辣", "过敏"],
82
+ "预算范围": ["预算", "大约", "左右"]
83
+ }
84
+
85
+ for key, keywords in preference_keywords.items():
86
+ for keyword in keywords:
87
+ if keyword in message_lower:
88
+ self.add_preference(user_id, key, message)
89
+ break
90
+
91
+
92
+ memory_system = MemorySystem()