Jason4Cheese commited on
Commit
7035ccd
·
verified ·
1 Parent(s): 50f7326

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +318 -0
app.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from pathlib import Path
4
+ from datetime import datetime
5
+ from typing import List, Dict, Any
6
+
7
+ import numpy as np
8
+ from sklearn.feature_extraction.text import TfidfVectorizer
9
+ from sklearn.metrics.pairwise import cosine_similarity
10
+
11
+ try:
12
+ import gradio as gr
13
+ GRADIO_AVAILABLE = True
14
+ except Exception:
15
+ GRADIO_AVAILABLE = False
16
+
17
+ try:
18
+ from huggingface_hub import hf_hub_upload
19
+ HF_AVAILABLE = True
20
+ except Exception:
21
+ HF_AVAILABLE = False
22
+
23
+ DB_PATH = Path("synchronicities.json")
24
+
25
+
26
+ class SynchronicityDB:
27
+ def __init__(self, path: Path = DB_PATH):
28
+ self.path = path
29
+ if not self.path.exists():
30
+ self._write({"entries": []})
31
+ self._data = self._read()
32
+
33
+ def _read(self):
34
+ with open(self.path, "r", encoding="utf-8") as f:
35
+ return json.load(f)
36
+
37
+ def _write(self, data):
38
+ with open(self.path, "w", encoding="utf-8") as f:
39
+ json.dump(data, f, indent=2, ensure_ascii=False)
40
+
41
+ def add_entry(self, text: str, tags: List[str], outcome: str = "", witness: str = "Asset 448804922"):
42
+ entry = {
43
+ "id": len(self._data["entries"]) + 1,
44
+ "timestamp": datetime.utcnow().isoformat() + "Z",
45
+ "text": text,
46
+ "tags": tags,
47
+ "outcome": outcome,
48
+ "witness": witness,
49
+ }
50
+ self._data["entries"].append(entry)
51
+ self._write(self._data)
52
+ return entry
53
+
54
+ def all_texts(self) -> List[str]:
55
+ return [e["text"] for e in self._data["entries"]]
56
+
57
+ def all_entries(self) -> List[Dict[str, Any]]:
58
+ return self._data["entries"]
59
+
60
+ def export_json(self) -> str:
61
+ return json.dumps(self._data, indent=2, ensure_ascii=False)
62
+
63
+ def reset(self):
64
+ self._write({"entries": []})
65
+ return True
66
+
67
+
68
+ def extract_tfidf_matrix(texts: List[str]):
69
+ if not texts:
70
+ return None, None
71
+ vect = TfidfVectorizer(max_features=2000, stop_words="english")
72
+ mat = vect.fit_transform(texts)
73
+ return mat, vect
74
+
75
+
76
+ def find_similar(new_text: str, db_texts: List[str], top_k: int = 5):
77
+ if not db_texts:
78
+ return []
79
+ texts = db_texts + [new_text]
80
+ mat, _ = extract_tfidf_matrix(texts)
81
+ if mat is None:
82
+ return []
83
+ sims = cosine_similarity(mat[-1], mat[:-1]).flatten()
84
+ idx_sorted = np.argsort(-sims)
85
+ results = []
86
+ for i in idx_sorted[:top_k]:
87
+ results.append({"index": int(i), "score": float(sims[i])})
88
+ return results
89
+
90
+
91
+ def coherence_score(matches: List[Dict[str, float]]):
92
+ if not matches:
93
+ return 0.0
94
+ return float(np.mean([m["score"] for m in matches]))
95
+
96
+
97
+ def predict_outcomes(matches: List[Dict[str, Any]], db_entries: List[Dict[str, Any]]):
98
+ if not matches:
99
+ return "No prediction — not enough history."
100
+ outcomes = []
101
+ tag_counts: Dict[str, int] = {}
102
+ for m in matches:
103
+ idx = m.get("index")
104
+ if idx is None:
105
+ continue
106
+ if idx < 0 or idx >= len(db_entries):
107
+ continue
108
+ e = db_entries[idx]
109
+ if e.get("outcome"):
110
+ outcomes.append(e["outcome"])
111
+ for t in e.get("tags", []):
112
+ tag_counts[t] = tag_counts.get(t, 0) + 1
113
+
114
+ suggestion_parts: List[str] = []
115
+ if outcomes:
116
+ from collections import Counter
117
+ c = Counter(outcomes)
118
+ top_outcome, cnt = c.most_common(1)[0]
119
+ suggestion_parts.append(f"Observed outcome pattern: '{top_outcome}' (seen {cnt} times among similar entries)")
120
+
121
+ if tag_counts:
122
+ sorted_tags = sorted(tag_counts.items(), key=lambda x: -x[1])
123
+ top_tags = [t for t, _ in sorted_tags[:3]]
124
+ suggestion_parts.append(f"Recurring tags among similar events: {', '.join(top_tags)}")
125
+
126
+ if not suggestion_parts:
127
+ return "No clear prediction from similar entries. Consider recording outcomes for better forecasts."
128
+
129
+ return " | ".join(suggestion_parts)
130
+
131
+
132
+ def upload_db_to_hf(file_path: Path, repo_id: str, token: str, commit_message: str = "Update synchronicities.json"):
133
+ if not HF_AVAILABLE:
134
+ return False, "huggingface_hub not installed"
135
+ if not token:
136
+ return False, "No HF token provided"
137
+ try:
138
+ with open(file_path, "rb") as f:
139
+ hf_hub_upload(repo_id=repo_id, path_or_fileobj=f, path_in_repo="synchronicities.json", token=token, repo_type="space")
140
+ return True, "Uploaded to Hugging Face Hub"
141
+ except Exception as e:
142
+ return False, str(e)
143
+
144
+
145
+ # Core logic
146
+
147
+ db = SynchronicityDB()
148
+
149
+
150
+ def bot_response(user_message: str) -> str:
151
+ lines = [l.strip() for l in user_message.splitlines() if l.strip()]
152
+ tags: List[str] = []
153
+ outcome = ""
154
+ text_lines: List[str] = []
155
+ for ln in lines:
156
+ if ln.upper().startswith("TAGS:"):
157
+ tags = [t.strip() for t in ln.split(":", 1)[1].split(",") if t.strip()]
158
+ elif ln.upper().startswith("OUTCOME:"):
159
+ outcome = ln.split(":", 1)[1].strip()
160
+ else:
161
+ text_lines.append(ln)
162
+
163
+ text = " ".join(text_lines).strip()
164
+
165
+ if not text:
166
+ return "I didn't catch the event text. Please describe the synchronicity."
167
+
168
+ entry = db.add_entry(text=text, tags=tags, outcome=outcome)
169
+
170
+ db_texts = db.all_texts()[:-1]
171
+ matches = find_similar(new_text=text, db_texts=db_texts, top_k=5)
172
+ score = coherence_score(matches)
173
+
174
+ assistant_parts: List[str] = []
175
+ assistant_parts.append("🌙 — The Oracle records your entry into the ledger of coincidence.")
176
+ assistant_parts.append(f"A coherence whisper: {score:.3f} (0–1, higher means more resonance with past entries)")
177
+
178
+ if matches:
179
+ assistant_parts.append("I perceive echoes from the archive:")
180
+ for m in matches:
181
+ idx = m.get("index")
182
+ if idx is None:
183
+ continue
184
+ if idx < 0 or idx >= len(db.all_entries()):
185
+ continue
186
+ e = db.all_entries()[idx]
187
+ snippet = e["text"][:180] + ("..." if len(e["text"]) > 180 else "")
188
+ assistant_parts.append(f"— {snippet} (score {m['score']:.3f}) — tags: {', '.join(e.get('tags', []))}")
189
+
190
+ prediction = predict_outcomes(matches, db.all_entries())
191
+ assistant_parts.append("Possible suggestion & pattern note:")
192
+ assistant_parts.append(prediction)
193
+
194
+ assistant_parts.append("If you wish to tag this as an observation only, add 'OUTCOME: none'. To attach tags, write 'TAGS: tag1, tag2' on a new line.")
195
+
196
+ assistant = "\n\n".join(assistant_parts)
197
+
198
+ hf_token = os.environ.get("HF_TOKEN")
199
+ hf_repo = os.environ.get("HF_REPO")
200
+ if hf_token and hf_repo:
201
+ ok, msg = upload_db_to_hf(DB_PATH, hf_repo, hf_token)
202
+ if ok:
203
+ assistant += "\n\n📡 The ledger was synchronized with your Hugging Face Space."
204
+ else:
205
+ assistant += f"\n\n⚠️ Sync to Hugging Face failed: {msg}"
206
+
207
+ return assistant
208
+
209
+
210
+ def reset_db_action():
211
+ db.reset()
212
+ return "Database cleared."
213
+
214
+
215
+ def export_db_action():
216
+ return db.export_json()
217
+
218
+
219
+ if GRADIO_AVAILABLE:
220
+ with gr.Blocks(title="Quantum Synchronicity Chatbot") as demo:
221
+ gr.Markdown("# Quantum Synchronicity Chatbot — Oracle Interface")
222
+ gr.Markdown("A mystical-toned chat interface. To add an entry, simply paste the description. Optional lines:\nTAGS: mirror, 11:11\nOUTCOME: travel_home\n\nIf HF_TOKEN and HF_REPO are set as environment variables, the database will try to sync after each entry.")
223
+
224
+ chatbot = gr.Chatbot(label="Oracle")
225
+ msg = gr.Textbox(placeholder="Type your synchronicity or question here...\n(You can add TAGS: and OUTCOME: on separate lines)")
226
+ clear = gr.Button("Clear chat")
227
+
228
+ with gr.Row():
229
+ add_btn = gr.Button("Add entry & analyze")
230
+ export_btn = gr.Button("Export DB JSON")
231
+ reset_btn = gr.Button("Reset DB")
232
+
233
+ db_output = gr.Textbox(label="Database (JSON export)", lines=8)
234
+
235
+ def user_submit(user_input, history):
236
+ history = history or []
237
+ assistant_text = bot_response(user_input)
238
+ history.append((user_input, assistant_text))
239
+ return history
240
+
241
+ add_btn.click(fn=user_submit, inputs=[msg, chatbot], outputs=[chatbot])
242
+ export_btn.click(fn=export_db_action, inputs=None, outputs=[db_output])
243
+ reset_btn.click(fn=reset_db_action, inputs=None, outputs=[db_output])
244
+
245
+ clear.click(lambda: [], None, chatbot)
246
+
247
+ if __name__ == "__main__":
248
+ demo.launch()
249
+ else:
250
+ def cli_help():
251
+ print("Gradio is not installed in this environment. Running in CLI fallback mode.")
252
+ print("Commands:\n add - Add a new synchronicity\n export - Print DB JSON\n reset - Clear the DB\n tests - Run basic tests\n exit - Quit")
253
+
254
+ def cli_loop():
255
+ cli_help()
256
+ while True:
257
+ cmd = input("> ").strip()
258
+ if not cmd:
259
+ continue
260
+ if cmd == "exit":
261
+ break
262
+ if cmd == "help":
263
+ cli_help()
264
+ continue
265
+ if cmd == "add":
266
+ print("Enter your synchronicity text (end with a blank line):")
267
+ lines = []
268
+ while True:
269
+ try:
270
+ ln = input()
271
+ except EOFError:
272
+ ln = ""
273
+ if ln.strip() == "":
274
+ break
275
+ lines.append(ln)
276
+ text = " ".join(lines).strip()
277
+ print("Optional: enter TAGS: comma,separated or leave blank:")
278
+ tags_line = input().strip()
279
+ tags = [t.strip() for t in tags_line.split(",") if t.strip()] if tags_line else []
280
+ print("Optional: enter OUTCOME: or leave blank:")
281
+ outcome = input().strip()
282
+ assistant = bot_response(f"{text}\nTAGS: {', '.join(tags)}\nOUTCOME: {outcome}")
283
+ print("\n---\n")
284
+ print(assistant)
285
+ print("\n---\n")
286
+ continue
287
+ if cmd == "export":
288
+ print(export_db_action())
289
+ continue
290
+ if cmd == "reset":
291
+ print(reset_db_action())
292
+ continue
293
+ if cmd == "tests":
294
+ run_tests()
295
+ continue
296
+ print("Unknown command. Type 'help' for options.")
297
+
298
+ def run_tests():
299
+ import tempfile
300
+ print("Running basic tests...")
301
+ with tempfile.TemporaryDirectory() as td:
302
+ test_path = Path(td) / "test_db.json"
303
+ test_db = SynchronicityDB(path=test_path)
304
+ assert test_db.all_entries() == []
305
+ e1 = test_db.add_entry("Saw mirror, 11:11 on the train", ["mirror", "11:11"], outcome="trip")
306
+ assert e1["id"] == 1
307
+ e2 = test_db.add_entry("Heard same song twice", ["song"], outcome="meeting")
308
+ assert e2["id"] == 2
309
+ texts = test_db.all_texts()
310
+ assert len(texts) == 2
311
+ sims = find_similar("Saw mirror again", texts, top_k=2)
312
+ assert isinstance(sims, list)
313
+ print("All tests passed.")
314
+
315
+ if __name__ == "__main__":
316
+ print("Gradio not available. To use the web UI, install gradio (`pip install gradio`).")
317
+ print("If you'd like me to change expected behavior for any command, tell me in chat.")
318
+ cli_loop()