Przemyslaw Chachaj commited on
Commit
a98fb2d
·
1 Parent(s): 00f98d2

Add habit engine with MCP tools

Browse files
Files changed (2) hide show
  1. modules/habits.py +149 -0
  2. requirements.txt +2 -1
modules/habits.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+ from pathlib import Path
4
+ from typing import Dict, Any, Optional
5
+
6
+ # Prosty storage w pliku JSON (działa w Space tak długo, jak instancja żyje)
7
+ STORAGE_PATH = Path(__file__).resolve().parent.parent / "storage" / "habits.json"
8
+ STORAGE_PATH.parent.mkdir(parents=True, exist_ok=True)
9
+
10
+ # Opcjonalne: zaawansowany pipeline z reels-agent (transkrypcja, summary, Nebius)
11
+ try:
12
+ from . import transcriber, summarizer # type: ignore
13
+ except Exception:
14
+ transcriber = None
15
+ summarizer = None
16
+
17
+
18
+ def _load_db() -> Dict[str, Any]:
19
+ if not STORAGE_PATH.exists():
20
+ return {}
21
+ try:
22
+ with STORAGE_PATH.open("r", encoding="utf-8") as f:
23
+ return json.load(f)
24
+ except Exception:
25
+ return {}
26
+
27
+
28
+ def _save_db(db: Dict[str, Any]) -> None:
29
+ with STORAGE_PATH.open("w", encoding="utf-8") as f:
30
+ json.dump(db, f, ensure_ascii=False, indent=2)
31
+
32
+
33
+ def _build_initial_step(link: str, category: str) -> str:
34
+ """
35
+ Tu próbujemy użyć starego pipeline'u (reels-agent):
36
+ - transkrypcja
37
+ - summary → konkretna akcja
38
+
39
+ Jeśli te funkcje nie istnieją / wywalą się → fallback na prosty tekst.
40
+ """
41
+ # Fallback – zawsze działa
42
+ fallback = "Obejrzyj materiał i zapisz 1 konkretną rzecz, którą dziś wdrożysz."
43
+
44
+ # Jeśli nie ma modułów – wracamy fallback
45
+ if transcriber is None or summarizer is None:
46
+ return fallback
47
+
48
+ try:
49
+ # PRZYKŁADOWA KONWENCJA – dopasujesz w razie czego u siebie:
50
+ # transcriber.transcribe_link(link) -> str (pełna transkrypcja)
51
+ # summarizer.summarize_to_action(text, category) -> str (krótka akcja)
52
+ if hasattr(transcriber, "transcribe_link") and hasattr(summarizer, "summarize_to_action"):
53
+ text = transcriber.transcribe_link(link) # type: ignore[attr-defined]
54
+ action = summarizer.summarize_to_action(text, category) # type: ignore[attr-defined]
55
+ if isinstance(action, str) and action.strip():
56
+ return action.strip()
57
+ except Exception:
58
+ # Nie zabijamy Space’a – po prostu wracamy prostą wersję
59
+ return fallback
60
+
61
+ return fallback
62
+
63
+
64
+ def create_contract(uid: str, link: str, category: str) -> Dict[str, Any]:
65
+ """
66
+ Tworzy / nadpisuje kontrakt nawyku:
67
+ - uid: ID usera (np. szef_1_career)
68
+ - link: IG/YT/TikTok
69
+ - category: 'career' / 'health' / 'relationships' / ...
70
+
71
+ Zwraca dict gotowy do wysłania jako JSON przez MCP.
72
+ """
73
+ db = _load_db()
74
+
75
+ initial_step = _build_initial_step(link, category)
76
+
77
+ contract = {
78
+ "uid": uid,
79
+ "link": link,
80
+ "category": category,
81
+ "created_at": int(time.time()),
82
+ "streak": 0,
83
+ "last_done_at": None,
84
+ "next_step": initial_step,
85
+ "history": [], # lista wykonanych kroków
86
+ }
87
+
88
+ db[uid] = contract
89
+ _save_db(db)
90
+ return contract
91
+
92
+
93
+ def get_contract(uid: str) -> Optional[Dict[str, Any]]:
94
+ db = _load_db()
95
+ return db.get(uid)
96
+
97
+
98
+ def complete_step(uid: str, note: Optional[str] = None) -> Dict[str, Any]:
99
+ """
100
+ User zrobił zadanie → zwiększamy streak, dopisujemy do historii
101
+ i generujemy kolejny krok (na razie prosty).
102
+ """
103
+ db = _load_db()
104
+ if uid not in db:
105
+ raise ValueError(f"Contract with uid={uid} not found")
106
+
107
+ contract = db[uid]
108
+ now = int(time.time())
109
+
110
+ # aktualizacja streak
111
+ contract["streak"] = int(contract.get("streak") or 0) + 1
112
+ contract["last_done_at"] = now
113
+
114
+ # dopisanie historii
115
+ contract.setdefault("history", [])
116
+ contract["history"].append(
117
+ {
118
+ "ts": now,
119
+ "done_step": contract.get("next_step"),
120
+ "note": note,
121
+ }
122
+ )
123
+
124
+ # nowy krok - tutaj możesz później wpiąć bardziej zaawansowany plan_generator
125
+ link = contract.get("link", "")
126
+ category = contract.get("category", "general")
127
+ contract["next_step"] = _build_initial_step(link, category)
128
+
129
+ db[uid] = contract
130
+ _save_db(db)
131
+ return contract
132
+
133
+
134
+ def get_next_step(uid: str) -> Dict[str, Any]:
135
+ """
136
+ Zwraca aktualny kontrakt i next_step dla danego uid.
137
+ """
138
+ contract = get_contract(uid)
139
+ if not contract:
140
+ raise ValueError(f"Contract with uid={uid} not found")
141
+
142
+ return {
143
+ "uid": contract["uid"],
144
+ "link": contract["link"],
145
+ "category": contract["category"],
146
+ "streak": contract.get("streak", 0),
147
+ "next_step": contract.get("next_step"),
148
+ "last_done_at": contract.get("last_done_at"),
149
+ }
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  gradio[mcp]
2
  yt-dlp
3
- openai>=1.40.0
 
 
1
  gradio[mcp]
2
  yt-dlp
3
+ openai>=1.40.0
4
+ requests