Princeaka commited on
Commit
06a1a2c
Β·
verified Β·
1 Parent(s): 07ad116

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +438 -316
app.py CHANGED
@@ -1,343 +1,465 @@
1
  #!/usr/bin/env python3
2
- # app.py - Front-end dashboard for Multimodular v7 (multimodal)
3
- # Place this file alongside your multimodular module (compact or expanded).
 
4
 
5
- import os, time, sys, json, pathlib
 
6
 
7
- # ---- Config: brain module names to try ----
8
- CANDIDATE_MODULES = [
9
- "multimodular_modul_v7", # compact name used earlier
10
- "multimodular_modul_v7_expanded", # expanded package name used earlier
11
- "multimodular_modul version 7.0", # fallback if you saved exact name (unlikely)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- # ---- Boot splash ----
15
- def boot_splash():
16
- os.system("cls" if os.name == "nt" else "clear")
17
- logo = r"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
19
  β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
20
  β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
21
- β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•
22
- β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘
23
- β•šβ•β•β•β•β•β•β•šβ•β• β•šβ•β•β•šβ•β•
24
- Close-to-Human Brain v7.0
25
  """
26
- print(logo)
27
- print("Initializing Universal Brain...")
 
 
 
28
  steps = [
 
29
  "Loading Core Modules",
30
- "Starting Local DB",
31
- "Bringing up CTB pipeline",
32
- "Starting Global Sync (if configured)",
33
- "Activating Creative Skill Vault",
34
- "Launching Dashboard"
35
  ]
36
  for s in steps:
37
- print(" β†’", s + "...")
38
- time.sleep(0.6)
39
- print("\nβœ… Ready!\n")
40
- time.sleep(0.3)
 
41
 
42
- # ---- Adaptive loader for your brain module ----
43
- def load_brain():
44
- for name in CANDIDATE_MODULES:
45
- try:
46
- mod = __import__(name)
47
- agent = None
48
- # common exported instances/names:
49
- if hasattr(mod, "AGENT"):
50
- agent = getattr(mod, "AGENT")
51
- elif hasattr(mod, "agent"):
52
- agent = getattr(mod, "agent")
53
- else:
54
- # try to instantiate a class if present
55
- cls_names = ["SuperAgentV7", "SuperAgent", "MultimodalBrain", "Agent", "Brain"]
56
- for cls in cls_names:
57
- if hasattr(mod, cls):
58
- try:
59
- agent = getattr(mod, cls)()
60
- break
61
- except Exception:
62
- agent = None
63
- # as last resort, if module defines functions, return module as agent
64
- if agent is None:
65
- agent = mod
66
- print(f"[INFO] Loaded brain module: {name}")
67
- return agent
68
- except Exception:
69
- continue
70
- print("[WARN] Could not auto-import expected brain module names.")
71
- print("Place your multimodular module in the same folder and name it one of:", ", ".join(CANDIDATE_MODULES))
72
- return None
73
 
74
- # ---- Helpers: flexible invocation for common brain actions ----
75
- def brain_call(agent, fn_names, *args, **kwargs):
76
- """Try to call first available function name on agent; return (ok, result)."""
77
- if agent is None:
78
- return False, "Brain not loaded"
79
- for fn in fn_names:
80
- if callable(getattr(agent, fn, None)):
81
- try:
82
- return True, getattr(agent, fn)(*args, **kwargs)
83
- except Exception as e:
84
- return False, f"error calling {fn}: {e}"
85
- # If agent itself exposes a 'ctb_handle' as attribute inside (e.g., agent.chb.ctb_handle)
86
- try:
87
- # try nested common path: agent.chb.ctb_handle
88
- chb = getattr(agent, "chb", None)
89
- if chb:
90
- for fn in fn_names:
91
- if callable(getattr(chb, fn, None)):
92
- try:
93
- return True, getattr(chb, fn)(*args, **kwargs)
94
- except Exception as e:
95
- return False, f"error calling chb.{fn}: {e}"
96
- except Exception:
97
- pass
98
- return False, f"none of {fn_names} found on agent"
99
-
100
- # ---- UI functions ----
101
- menus = {
102
- "1": "πŸ’¬ Chat with AI (All Features in One Chat)",
103
- "2": "πŸ”Ž Search Knowledge Base",
104
- "3": "πŸ“€ Upload Media for Learning",
105
- "4": "πŸ’Ύ Backup / Restore Brain (download backup)",
106
- "5": "🎨 View Creative Skill Vault (top skills)",
107
- "6": "πŸ” Global Brain Sync Status",
108
- "7": "πŸ›  Developer API Options",
109
- "8": "πŸ“΄ Offline Mode / Toggle",
110
- "9": "❌ Exit"
111
- }
112
-
113
- def show_menu():
114
- print("=== CHB v7.0 Main Menu ===")
115
- for k in sorted(menus.keys(), key=int):
116
- print(f"[{k}] {menus[k]}")
117
-
118
- # ---- Media helpers (simple) ----
119
- def read_file_as_payload(path):
120
  p = pathlib.Path(path)
121
- if not p.exists():
122
- return None, f"file not found: {path}"
123
- # minimal payload: path & size
124
- try:
125
- meta = {"path": str(p.resolve()), "size": p.stat().st_size}
126
- return {"path": str(p.resolve()), "meta": meta}, None
127
- except Exception as e:
128
- return None, f"read error: {e}"
129
-
130
- # ---- Menu 1: Multimodal chat loop ----
131
- def multimodal_chat(agent):
132
- print("\n=== Multimodal AI Chat ===")
133
- print("Type naturally. Special commands:")
134
- print(" /upload <path> - attach a file (image, video, audio)")
135
- print(" /search <query> - run user-device search (plan + return style)")
136
- print(" /skills <tag> - show top creative skills for tag")
137
- print(" /backup - create a new backup and show path")
138
- print(" /help - show this help")
139
- print(" /exit - return to main menu\n")
140
- while True:
141
- try:
142
- user = input("You: ").strip()
143
- except (KeyboardInterrupt, EOFError):
144
- print("\nReturning to main menu.")
145
- return
146
- if not user:
147
- continue
148
- if user.lower() in ("/exit", "exit", "quit"):
149
- print("Returning to main menu.\n")
150
- return
151
- if user.startswith("/upload "):
152
- path = user[len("/upload "):].strip().strip('"').strip("'")
153
- payload, err = read_file_as_payload(path)
154
- if err:
155
- print("Error:", err); continue
156
- # Build a simple plan_results-like structure and submit to brain
157
- # plan_results should include images/videos/audios lists if agent expects that shape
158
- plan_results = {}
159
- suffix = pathlib.Path(path).suffix.lower()
160
- if suffix in (".png", ".jpg", ".jpeg", ".webp", ".bmp"):
161
- plan_results["images"] = [{"path": payload["path"], "quality_score": 0.9, "caption": "", "tags": []}]
162
- elif suffix in (".mp4", ".mov", ".mkv", ".webm"):
163
- plan_results["videos"] = [{"path": payload["path"], "quality_score": 0.8, "caption": "", "tags": []}]
164
- elif suffix in (".mp3", ".wav", ".m4a", ".ogg"):
165
- plan_results["audios"] = [{"path": payload["path"], "quality_score": 0.8, "caption": "", "tags": []}]
166
- else:
167
- plan_results["files"] = [{"path": payload["path"], "meta": payload["meta"]}]
168
- ok, res = brain_call(agent, ["submit_plan_results", "handle_plan_results", "submit_results", "submit_plan"], plan_id="upload_"+str(int(time.time())), results=plan_results)
169
- if ok:
170
- print("AI: (processed upload) ->", res)
171
- else:
172
- print("AI: upload processed locally, but brain call failed:", res)
173
- continue
174
- if user.startswith("/search "):
175
- q = user[len("/search "):].strip()
176
- ok, plan = brain_call(agent, ["plan_search", "plan"], q)
177
- if ok:
178
- print("AI: Generated search plan. (Run this plan on client and submit results.)")
179
- print(json.dumps(plan, indent=2) if isinstance(plan, dict) else plan)
180
- else:
181
- print("AI: search plan generation failed:", plan)
182
- continue
183
- if user.startswith("/skills "):
184
- tag = user[len("/skills "):].strip()
185
- ok, skills = brain_call(agent, ["top_skills", "top_skill", "top_by_tag"], tag, 5)
186
- if ok:
187
- print("Top skills for", tag, ":", skills)
188
- else:
189
- print("Could not fetch skills:", skills)
190
- continue
191
- if user.strip() == "/backup":
192
- ok, path = brain_call(agent, ["download_latest_backup", "latest_backup", "get_latest_backup"])
193
- if ok and path:
194
- print("Latest backup path:", path)
195
- else:
196
- # try to create a new backup if method available
197
- ok2, created = brain_call(agent, ["backup_create", "create_backup", "create_backup_zip"])
198
- if ok2:
199
- print("Created backup:", created)
200
- else:
201
- print("Backup not available:", path or created)
202
- continue
203
- if user.strip() == "/help":
204
- print("Commands: /upload, /search, /skills, /backup, /exit")
205
- continue
206
 
207
- # Regular freeform input: call ctb_handle if present, else agent.chat or agent.chat()
208
- # Prefer 'ctb_handle' (Close-to-Human Brain multimodal pipeline), fall back to 'chat' or 'plan_search'
209
- ok, resp = brain_call(agent, ["ctb_handle", "handle_input", "chat", "chat_message", "chat_query"], input_data=user)
210
- if not ok:
211
- # try more permissive call signatures
212
  try:
213
- # some agents expect chat(text)
214
- resp = agent.chat(user)
215
- print("AI:", resp)
216
- except Exception as e:
217
- print("AI call failed:", resp)
218
- else:
219
- print("AI:", resp)
220
-
221
- # ---- Menus 2..9 simple wrappers that call brain functions if present ----
222
- def menu_search_kb(agent):
223
- q = input("Enter search query: ").strip()
224
- if not q: return
225
- ok, res = brain_call(agent, ["search_facts", "facts_search", "query_facts"], q)
226
- if ok:
227
- print("Results:", res)
228
- else:
229
- print("Search failed:", res)
230
-
231
- def menu_upload_media(agent):
232
- path = input("Path to media file: ").strip()
233
- if not path: return
234
- payload, err = read_file_as_payload(path)
235
- if err:
236
- print("Error:", err); return
237
- # submit via same upload command as chat
238
- plan_results = {}
239
- suffix = pathlib.Path(path).suffix.lower()
240
- if suffix in (".png", ".jpg", ".jpeg", ".webp", ".bmp"):
241
- plan_results["images"] = [{"path": payload["path"], "quality_score": 0.9}]
242
- elif suffix in (".mp4", ".mov", ".mkv"):
243
- plan_results["videos"] = [{"path": payload["path"], "quality_score": 0.8}]
244
- elif suffix in (".mp3", ".wav"):
245
- plan_results["audios"] = [{"path": payload["path"], "quality_score": 0.8}]
246
- else:
247
- plan_results["files"] = [{"path": payload["path"], "meta": payload["meta"]}]
248
- ok, res = brain_call(agent, ["submit_plan_results", "handle_plan_results"], plan_id="manual_upload_"+str(int(time.time())), results=plan_results)
249
- if ok:
250
- print("Upload processed:", res)
251
- else:
252
- print("Upload failed:", res)
253
 
254
- def menu_backup_download(agent):
255
- ok, p = brain_call(agent, ["download_latest_backup", "latest_backup", "get_latest_backup"])
256
- if ok and p:
257
- print("Latest backup:", p)
258
- else:
259
- print("No backup available or call failed:", p)
 
 
 
 
 
 
 
 
 
260
 
261
- def menu_view_vault(agent):
262
- tag = input("Enter skill tag (or blank to list all): ").strip()
263
- if tag:
264
- ok, s = brain_call(agent, ["top_skills", "top_by_tag"], tag, 10)
265
- else:
266
- ok, s = brain_call(agent, ["list_skills", "get_skills"], )
267
- if ok:
268
- print("Skills:", s)
269
- else:
270
- print("Failed to retrieve skills:", s)
271
 
272
- def menu_sync_status(agent):
273
- ok, st = brain_call(agent, ["global_sync_status", "sync_status", "get_sync_status"])
274
- if ok:
275
- print("Global Sync Status:", st)
276
- else:
277
- print("Global sync status not available:", st)
278
-
279
- def menu_dev_api(agent):
280
- print("Developer API options:")
281
- print(" 1) Add/Integrate module from file")
282
- print(" 2) List modules")
283
- choice = input("choice: ").strip()
284
- if choice == "1":
285
- path = input("Path to module (py or base64-wasm): ").strip()
286
- payload, err = read_file_as_payload(path)
287
- if err:
288
- print("Error:", err); return
289
- code = ""
290
- try:
291
- code = open(payload["path"], "rb").read().decode("utf-8")
292
- except Exception:
293
- import base64
294
- code = base64.b64encode(open(payload["path"], "rb").read()).decode()
295
- name = input("Module name (short): ").strip() or f"mod_{int(time.time())}"
296
- ok, res = brain_call(agent, ["add_module", "integrate_module"], name, code, None)
297
- print("Result:", res)
298
- elif choice == "2":
299
- ok, res = brain_call(agent, ["list_modules", "get_modules"])
300
- print("Modules:", res if ok else "failed:"+str(res))
301
- else:
302
- print("cancel")
303
 
304
- def menu_offline_toggle(agent):
305
- ok, st = brain_call(agent, ["toggle_offline", "set_offline", "offline_toggle"])
306
- if ok:
307
- print("Offline toggled:", st)
308
- else:
309
- print("Offline toggle not available; try starting/stopping network in your environment.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- # ---- Main loop ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  def main():
313
- boot_splash()
314
- agent = load_brain()
315
- if agent is None:
316
- print("Brain not loaded. You can still use app UI, but brain-dependent actions will fail.")
317
- while True:
318
- show_menu()
319
- choice = input("Select: ").strip()
320
- if choice == "1":
321
- multimodal_chat(agent)
322
- elif choice == "2":
323
- menu_search_kb(agent)
324
- elif choice == "3":
325
- menu_upload_media(agent)
326
- elif choice == "4":
327
- menu_backup_download(agent)
328
- elif choice == "5":
329
- menu_view_vault(agent)
330
- elif choice == "6":
331
- menu_sync_status(agent)
332
- elif choice == "7":
333
- menu_dev_api(agent)
334
- elif choice == "8":
335
- menu_offline_toggle(agent)
336
- elif choice == "9":
337
- print("Goodbye.")
338
- break
339
- else:
340
- print("Unknown option; try again.\n")
 
 
341
 
342
  if __name__ == "__main__":
343
  main()
 
1
  #!/usr/bin/env python3
2
+ # app.py β€” CHB v7.1 Professional Frontend (CLI + API, animated splash, ChatGPT-style TUI)
3
+ # Requires: rich, fastapi, uvicorn
4
+ # Brain file expected: multimodular_modul_v7.py (same folder)
5
 
6
+ import os, sys, time, json, argparse, pathlib, base64, random, string, datetime, threading
7
+ from typing import Any, Dict, Tuple, Optional
8
 
9
+ # -------------------- Optional deps (graceful) --------------------
10
+ try:
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.table import Table
14
+ from rich.layout import Layout
15
+ from rich.live import Live
16
+ from rich.align import Align
17
+ from rich.markdown import Markdown
18
+ from rich.text import Text
19
+ from rich.box import ROUNDED
20
+ except Exception as e:
21
+ print("Missing 'rich'. Install: pip install rich")
22
+ raise
23
+
24
+ try:
25
+ from fastapi import FastAPI, Request, HTTPException
26
+ import uvicorn
27
+ except Exception:
28
+ FastAPI = None # API disabled if FastAPI not installed
29
+
30
+ # -------------------- Load brain module --------------------
31
+ BRAIN_CANDIDATES = [
32
+ "multimodular_modul_v7", # provided by user
33
  ]
34
+ def load_brain():
35
+ for name in BRAIN_CANDIDATES:
36
+ try:
37
+ mod = __import__(name)
38
+ # Common exports
39
+ if hasattr(mod, "AGENT"): return getattr(mod, "AGENT")
40
+ if hasattr(mod, "agent"): return getattr(mod, "agent")
41
+ for cls in ("SuperAgentV7","MultimodalBrain","SuperAgent","Agent","Brain"):
42
+ if hasattr(mod, cls):
43
+ return getattr(mod, cls)()
44
+ return mod
45
+ except Exception:
46
+ continue
47
+ return None
48
 
49
+ BRAIN = load_brain()
50
+
51
+ def brain_call(fn_candidates, *args, **kwargs) -> Tuple[bool, Any]:
52
+ """Try multiple function names on brain (and nested .chb)."""
53
+ if BRAIN is None: return False, "Brain not loaded"
54
+ # direct on brain
55
+ for fn in fn_candidates:
56
+ f = getattr(BRAIN, fn, None)
57
+ if callable(f):
58
+ try: return True, f(*args, **kwargs)
59
+ except Exception as e: return False, f"{fn} error: {e}"
60
+ # nested .chb
61
+ chb = getattr(BRAIN, "chb", None)
62
+ if chb:
63
+ for fn in fn_candidates:
64
+ f = getattr(chb, fn, None)
65
+ if callable(f):
66
+ try: return True, f(*args, **kwargs)
67
+ except Exception as e: return False, f"chb.{fn} error: {e}"
68
+ return False, f"Missing: {', '.join(fn_candidates)}"
69
+
70
+ # -------------------- API Keys & Limits --------------------
71
+ API_KEYS_FILE = "api_keys.json"
72
+ DAILY_LIMIT = 1000
73
+ API_LOCK = threading.Lock()
74
+ def _load_keys() -> Dict[str, Dict[str, Any]]:
75
+ if os.path.exists(API_KEYS_FILE):
76
+ try:
77
+ return json.load(open(API_KEYS_FILE,"r"))
78
+ except Exception:
79
+ return {}
80
+ return {}
81
+ def _save_keys(data: Dict[str, Any]):
82
+ json.dump(data, open(API_KEYS_FILE,"w"), indent=2)
83
+ def _today() -> str:
84
+ return datetime.date.today().isoformat()
85
+
86
+ def api_key_generate() -> str:
87
+ with API_LOCK:
88
+ data = _load_keys()
89
+ key = ''.join(random.choices(string.ascii_letters + string.digits, k=25))
90
+ data[key] = {"usage": 0, "last_reset": _today(), "created": datetime.datetime.utcnow().isoformat()+"Z"}
91
+ _save_keys(data)
92
+ return key
93
+
94
+ def api_key_revoke(key: str) -> bool:
95
+ with API_LOCK:
96
+ data = _load_keys()
97
+ if key in data:
98
+ del data[key]
99
+ _save_keys(data)
100
+ return True
101
+ return False
102
+
103
+ def api_key_validate_and_count(key: str) -> bool:
104
+ with API_LOCK:
105
+ data = _load_keys()
106
+ if key not in data: return False
107
+ # reset day
108
+ if data[key].get("last_reset") != _today():
109
+ data[key]["usage"] = 0
110
+ data[key]["last_reset"] = _today()
111
+ if data[key]["usage"] >= DAILY_LIMIT: return False
112
+ data[key]["usage"] += 1
113
+ _save_keys(data)
114
+ return True
115
+
116
+ def api_stats() -> Dict[str, Any]:
117
+ with API_LOCK:
118
+ data = _load_keys()
119
+ return {
120
+ "active_keys": len(data),
121
+ "limit_per_day": DAILY_LIMIT,
122
+ "keys": data
123
+ }
124
+
125
+ # -------------------- FastAPI (shared brain) --------------------
126
+ APP = None
127
+ if FastAPI:
128
+ APP = FastAPI(title="CHB Universal Brain API", version="7.1")
129
+
130
+ @APP.post("/chat")
131
+ async def api_chat(req: Request):
132
+ key = req.headers.get("x-api-key")
133
+ if not key or not api_key_validate_and_count(key):
134
+ raise HTTPException(status_code=401, detail="Invalid key or limit reached")
135
+ data = await req.json()
136
+ msg = data.get("message","")
137
+ ok, res = brain_call(["ctb_handle","process_input","chat","chat_message","handle_input"], input_data=msg)
138
+ if not ok: raise HTTPException(status_code=500, detail=str(res))
139
+ return {"reply": res}
140
+
141
+ @APP.post("/search")
142
+ async def api_search(req: Request):
143
+ key = req.headers.get("x-api-key")
144
+ if not key or not api_key_validate_and_count(key):
145
+ raise HTTPException(status_code=401, detail="Invalid key or limit reached")
146
+ q = (await req.json()).get("query","")
147
+ ok, res = brain_call(["search_kb","facts_search","search_facts","query_facts"], q)
148
+ if not ok: raise HTTPException(status_code=500, detail=str(res))
149
+ return {"results": res}
150
+
151
+ @APP.post("/upload")
152
+ async def api_upload(req: Request):
153
+ key = req.headers.get("x-api-key")
154
+ if not key or not api_key_validate_and_count(key):
155
+ raise HTTPException(status_code=401, detail="Invalid key or limit reached")
156
+ data = await req.json()
157
+ path = data.get("path")
158
+ b64 = data.get("base64")
159
+ tmp_path = None
160
+ if b64:
161
+ raw = base64.b64decode(b64)
162
+ tmp_path = f"upload_{int(time.time())}.bin"
163
+ open(tmp_path,"wb").write(raw)
164
+ path = tmp_path
165
+ if not path: raise HTTPException(status_code=400, detail="Provide 'path' or 'base64'")
166
+ payload = _plan_results_for_path(path)
167
+ ok, res = brain_call(["submit_plan_results","handle_plan_results","submit_plan"], plan_id=f"api_upload_{int(time.time())}", results=payload)
168
+ if tmp_path and os.path.exists(tmp_path): os.remove(tmp_path)
169
+ if not ok: raise HTTPException(status_code=500, detail=str(res))
170
+ return {"status":"ok","details":res}
171
+
172
+ @APP.get("/skills")
173
+ def api_skills(tag: Optional[str] = None, top: int = 10):
174
+ # no key required here? Make it protected too:
175
+ raise HTTPException(status_code=405, detail="Use POST /skills with x-api-key")
176
+
177
+ @APP.post("/skills")
178
+ async def api_skills_post(req: Request):
179
+ key = req.headers.get("x-api-key")
180
+ if not key or not api_key_validate_and_count(key):
181
+ raise HTTPException(status_code=401, detail="Invalid key or limit reached")
182
+ data = await req.json()
183
+ tag = data.get("tag","")
184
+ top = int(data.get("top",10))
185
+ if tag:
186
+ ok, res = brain_call(["top_skills","top_by_tag"], tag, top)
187
+ else:
188
+ ok, res = brain_call(["list_skills","get_skills"])
189
+ if not ok: raise HTTPException(status_code=500, detail=str(res))
190
+ return {"skills": res}
191
+
192
+ @APP.get("/backup")
193
+ def api_backup_get():
194
+ return {"detail":"Use POST /backup with x-api-key"}
195
+
196
+ @APP.post("/backup")
197
+ async def api_backup(req: Request):
198
+ key = req.headers.get("x-api-key")
199
+ if not key or not api_key_validate_and_count(key):
200
+ raise HTTPException(status_code=401, detail="Invalid key or limit reached")
201
+ ok, path = brain_call(["download_latest_backup","latest_backup","get_latest_backup"])
202
+ if not ok or not path:
203
+ ok2, created = brain_call(["backup_create","create_backup","create_backup_zip"])
204
+ if not ok2: raise HTTPException(status_code=500, detail=str(created))
205
+ path = created
206
+ return {"backup_path": path}
207
+
208
+ @APP.get("/status")
209
+ def api_status():
210
+ # Public status (no key): only meta
211
+ meta = {"api": "online", "keys": api_stats()["active_keys"], "limit_per_day": DAILY_LIMIT}
212
+ return meta
213
+
214
+ @APP.get("/key/new")
215
+ def api_key_new():
216
+ return {"api_key": api_key_generate()}
217
+
218
+ @APP.post("/key/revoke")
219
+ async def api_key_revoke(req: Request):
220
+ data = await req.json()
221
+ key = data.get("api_key")
222
+ if not key: raise HTTPException(status_code=400, detail="api_key required")
223
+ ok = api_key_revoke(key)
224
+ if not ok: raise HTTPException(status_code=404, detail="Key not found")
225
+ return {"status":"revoked"}
226
+
227
+ # -------------------- CLI (ChatGPT-style TUI) --------------------
228
+ console = Console()
229
+
230
+ CHB_ASCII = """
231
  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
232
  β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
233
  β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
234
+ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
235
+ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
236
+ β•šβ•β•β•β•β•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•
 
237
  """
238
+
239
+ def boot_splash():
240
+ console.clear()
241
+ panel = Panel.fit(Text(CHB_ASCII, style="bold cyan"), title="Close-to-Human Brain v7.1", border_style="cyan", padding=(1,4))
242
+ console.print(panel, justify="center")
243
  steps = [
244
+ "Initializing Universal Brain",
245
  "Loading Core Modules",
246
+ "Connecting Global Sync",
247
+ "Activating CTB Pipeline",
248
+ "Preparing Creative Skill Vault"
 
 
249
  ]
250
  for s in steps:
251
+ console.print(f"[cyan]β†’ {s}...[/cyan]")
252
+ time.sleep(0.5)
253
+ console.print("[bold green]βœ… Ready![/bold green]")
254
+ time.sleep(0.6)
255
+ console.clear()
256
 
257
+ def _layout() -> Layout:
258
+ layout = Layout()
259
+ layout.split(
260
+ Layout(name="header", size=5),
261
+ Layout(name="body", ratio=1),
262
+ Layout(name="footer", size=3),
263
+ )
264
+ layout["body"].split_row(
265
+ Layout(name="sidebar", size=26),
266
+ Layout(name="chat", ratio=1)
267
+ )
268
+ return layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
+ SIDEBAR_ITEMS = [
271
+ ("πŸ’¬", "Multimodal Chat"),
272
+ ("πŸ“š", "Knowledge Search"),
273
+ ("πŸ“€", "Upload"),
274
+ ("πŸ’Ύ", "Backup/Restore"),
275
+ ("🎨", "Creative Vault"),
276
+ ("🌍", "Sync Status"),
277
+ ("πŸ› ", "API Keys"),
278
+ ("πŸ“΄", "Offline Mode")
279
+ ]
280
+
281
+ CHAT_HISTORY = []
282
+
283
+ def status_bar() -> Text:
284
+ stats = api_stats()
285
+ sync_ok, sync = brain_call(["global_sync_status","sync_status","get_sync_status"])
286
+ sync_txt = "Active" if (sync_ok and str(sync).lower().find("off")==-1) else "Idle"
287
+ t = Text(f"🌐 API: {'ON' if APP else 'OFF'} πŸ”‘ Keys: {stats.get('active_keys',0)} πŸ“Š Sync: {sync_txt} ⏳ Limit: {DAILY_LIMIT}/day")
288
+ t.stylize("cyan")
289
+ return t
290
+
291
+ def sidebar_panel(active: str) -> Panel:
292
+ table = Table.grid(padding=(0,1))
293
+ for icon, label in SIDEBAR_ITEMS:
294
+ style = "bold white" if label==active else "grey70"
295
+ table.add_row(Text(f"{icon} {label}", style=style))
296
+ return Panel(table, title="CHB", border_style="cyan", box=ROUNDED)
297
+
298
+ def header_panel() -> Panel:
299
+ return Panel(Text("CHB Universal Brain v7.1", style="bold cyan"), border_style="cyan", box=ROUNDED)
300
+
301
+ def chat_panel() -> Panel:
302
+ md = ""
303
+ for role, msg in CHAT_HISTORY[-100:]:
304
+ if role=="user": md += f"**You:** {msg}\n\n"
305
+ else: md += f"**CHB:** {msg}\n\n"
306
+ if not md:
307
+ md = "**CHB:** Hello! Type your message. Use `/help` for commands.\n"
308
+ return Panel(Markdown(md), border_style="cyan", box=ROUNDED)
309
+
310
+ def footer_panel() -> Panel:
311
+ return Panel(status_bar(), border_style="cyan", box=ROUNDED)
312
+
313
+ def _plan_results_for_path(path: str) -> Dict[str, Any]:
 
 
314
  p = pathlib.Path(path)
315
+ suffix = p.suffix.lower()
316
+ payload = {}
317
+ if suffix in (".png",".jpg",".jpeg",".webp",".bmp"):
318
+ payload["images"] = [{"path": str(p), "quality_score": 0.9, "caption": "", "tags": []}]
319
+ elif suffix in (".mp4",".mov",".mkv",".webm"):
320
+ payload["videos"] = [{"path": str(p), "quality_score": 0.8, "caption": "", "tags": []}]
321
+ elif suffix in (".mp3",".wav",".m4a",".ogg"):
322
+ payload["audios"] = [{"path": str(p), "quality_score": 0.8, "caption": "", "tags": []}]
323
+ else:
324
+ payload["files"] = [{"path": str(p), "meta": {"size": p.stat().st_size if p.exists() else 0}}]
325
+ return payload
326
+
327
+ def run_cli():
328
+ boot_splash()
329
+ active = "Multimodal Chat"
330
+ layout = _layout()
331
+ layout["header"].update(header_panel())
332
+ layout["sidebar"].update(sidebar_panel(active))
333
+ layout["chat"].update(chat_panel())
334
+ layout["footer"].update(footer_panel())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
+ with Live(layout, refresh_per_second=10, screen=True):
337
+ while True:
 
 
 
338
  try:
339
+ console.print() # spacing under Live
340
+ user = console.input("[bold blue]You β€Ί [/bold blue]").strip()
341
+ except (KeyboardInterrupt, EOFError):
342
+ break
343
+ if not user: continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
+ # Slash commands (stay chat-first; sidebar is thematic)
346
+ if user == "/help":
347
+ help_text = (
348
+ "**Commands**\n"
349
+ "β€’ /upload <path> β€” process media/file\n"
350
+ "β€’ /search <query> β€” knowledge search\n"
351
+ "β€’ /backup β€” create/show latest backup\n"
352
+ "β€’ /status β€” show global sync status\n"
353
+ "β€’ /keys new|revoke <key>|list β€” manage API keys\n"
354
+ "β€’ /offline β€” toggle offline mode\n"
355
+ "β€’ /clear β€” clear chat\n"
356
+ "β€’ /quit β€” exit\n"
357
+ )
358
+ CHAT_HISTORY.append(("chb", help_text))
359
+ layout["chat"].update(chat_panel()); continue
360
 
361
+ if user.startswith("/upload "):
362
+ path = user.split(" ",1)[1].strip().strip('"').strip("'")
363
+ payload = _plan_results_for_path(path)
364
+ ok, res = brain_call(["submit_plan_results","handle_plan_results","submit_plan"], plan_id=f"cli_upload_{int(time.time())}", results=payload)
365
+ CHAT_HISTORY.append(("user", user))
366
+ CHAT_HISTORY.append(("chb", f"Upload processed: {res if ok else res}"))
367
+ layout["chat"].update(chat_panel()); continue
 
 
 
368
 
369
+ if user.startswith("/search "):
370
+ q = user.split(" ",1)[1].strip()
371
+ ok, res = brain_call(["search_kb","facts_search","search_facts","query_facts"], q)
372
+ CHAT_HISTORY.append(("user", user))
373
+ CHAT_HISTORY.append(("chb", f"Search results:\n{res if ok else res}"))
374
+ layout["chat"].update(chat_panel()); continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
376
+ if user == "/backup":
377
+ ok, path = brain_call(["download_latest_backup","latest_backup","get_latest_backup"])
378
+ if not (ok and path):
379
+ ok2, created = brain_call(["backup_create","create_backup","create_backup_zip"])
380
+ CHAT_HISTORY.append(("user","/backup"))
381
+ CHAT_HISTORY.append(("chb", f"Backup: {created if ok2 else path}"))
382
+ else:
383
+ CHAT_HISTORY.append(("user","/backup"))
384
+ CHAT_HISTORY.append(("chb", f"Latest backup: {path}"))
385
+ layout["chat"].update(chat_panel()); continue
386
+
387
+ if user == "/status":
388
+ ok, res = brain_call(["global_sync_status","sync_status","get_sync_status"])
389
+ CHAT_HISTORY.append(("user","/status"))
390
+ CHAT_HISTORY.append(("chb", f"Sync status: {res if ok else res}"))
391
+ layout["footer"].update(footer_panel())
392
+ layout["chat"].update(chat_panel()); continue
393
+
394
+ if user.startswith("/keys"):
395
+ parts = user.split()
396
+ if len(parts)==2 and parts[1]=="new":
397
+ k = api_key_generate()
398
+ CHAT_HISTORY.append(("user", user))
399
+ CHAT_HISTORY.append(("chb", f"New API key: `{k}`"))
400
+ elif len(parts)==3 and parts[1]=="revoke":
401
+ ok = api_key_revoke(parts[2])
402
+ CHAT_HISTORY.append(("user", user))
403
+ CHAT_HISTORY.append(("chb", "Key revoked" if ok else "Key not found"))
404
+ else:
405
+ stats = api_stats()
406
+ CHAT_HISTORY.append(("user", user))
407
+ CHAT_HISTORY.append(("chb", f"Keys: {list(stats.get('keys',{}).keys())}"))
408
+ layout["footer"].update(footer_panel())
409
+ layout["chat"].update(chat_panel()); continue
410
 
411
+ if user == "/offline":
412
+ ok, res = brain_call(["toggle_offline","set_offline","offline_toggle"])
413
+ CHAT_HISTORY.append(("user","/offline"))
414
+ CHAT_HISTORY.append(("chb", f"Offline toggled: {res if ok else res}"))
415
+ layout["footer"].update(footer_panel())
416
+ layout["chat"].update(chat_panel()); continue
417
+
418
+ if user == "/clear":
419
+ CHAT_HISTORY.clear()
420
+ layout["chat"].update(chat_panel()); continue
421
+
422
+ if user in ("/quit","/exit"):
423
+ break
424
+
425
+ # Normal chat -> brain
426
+ CHAT_HISTORY.append(("user", user))
427
+ ok, resp = brain_call(["ctb_handle","process_input","chat","chat_message","handle_input"], input_data=user)
428
+ CHAT_HISTORY.append(("chb", resp if ok else str(resp)))
429
+ layout["chat"].update(chat_panel())
430
+
431
+ # -------------------- Entry --------------------
432
  def main():
433
+ parser = argparse.ArgumentParser(description="CHB v7.1 Frontend")
434
+ parser.add_argument("--api", action="store_true", help="Run API server only")
435
+ parser.add_argument("--cli", action="store_true", help="Run CLI only")
436
+ parser.add_argument("--both", action="store_true", help="Run CLI and API together")
437
+ parser.add_argument("--host", default="0.0.0.0")
438
+ parser.add_argument("--port", type=int, default=8000)
439
+ args = parser.parse_args()
440
+
441
+ if args.api and not FastAPI:
442
+ console.print("[red]FastAPI/uvicorn not installed. Run: pip install fastapi uvicorn[/red]")
443
+ sys.exit(1)
444
+
445
+ if args.api and not args.cli and not args.both:
446
+ # API only
447
+ if APP is None:
448
+ console.print("[red]API unavailable.[/red]"); sys.exit(1)
449
+ uvicorn.run(APP, host=args.host, port=args.port)
450
+ return
451
+
452
+ if args.both and APP:
453
+ # Run API in background thread + CLI
454
+ def run_api():
455
+ uvicorn.run(APP, host=args.host, port=args.port, log_level="warning")
456
+ t = threading.Thread(target=run_api, daemon=True)
457
+ t.start()
458
+ run_cli()
459
+ return
460
+
461
+ # default: CLI only
462
+ run_cli()
463
 
464
  if __name__ == "__main__":
465
  main()