Spaces:
Sleeping
Sleeping
CAPT Agent commited on
Commit ยท
d2b9430
1
Parent(s): eb3bf93
๐ฅ Connect to real CAPT backend
Browse files- __pycache__/app.cpython-314.pyc +0 -0
- app.py +164 -24
- capt_backend.py +351 -0
- requirements.txt +1 -0
__pycache__/app.cpython-314.pyc
ADDED
|
Binary file (24.2 kB). View file
|
|
|
app.py
CHANGED
|
@@ -1,15 +1,56 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
import subprocess
|
| 5 |
import tempfile
|
| 6 |
import shutil
|
| 7 |
import os
|
|
|
|
|
|
|
| 8 |
from datetime import datetime
|
| 9 |
from typing import List, Dict
|
| 10 |
|
| 11 |
import gradio as gr
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
# โโ Git logic โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 14 |
|
| 15 |
def get_git_commits(repo_path: str, max_commits: int = 50) -> List[Dict]:
|
|
@@ -156,24 +197,33 @@ def render_summary(commits: List[Dict]) -> str:
|
|
| 156 |
|
| 157 |
# โโ Repo fetch โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 158 |
|
| 159 |
-
def fetch_repo(repo_url: str, max_commits: int) ->
|
| 160 |
-
"""Clone shallowly, run hologram,
|
| 161 |
repo_url = repo_url.strip()
|
| 162 |
if not repo_url:
|
| 163 |
-
return "<p style='color:#f66'>Please enter a repo URL.</p>"
|
| 164 |
|
| 165 |
-
# Normalize URL
|
| 166 |
if repo_url.startswith("http"):
|
| 167 |
if not repo_url.endswith(".git"):
|
| 168 |
repo_url += ".git"
|
| 169 |
elif repo_url.startswith("git@"):
|
| 170 |
-
pass
|
| 171 |
else:
|
| 172 |
-
# Assume GitHub username/repo shorthand
|
| 173 |
if "/" not in repo_url:
|
| 174 |
repo_url = f"knowurknot/{repo_url}"
|
| 175 |
repo_url = f"https://github.com/{repo_url}.git"
|
| 176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
tmpdir = tempfile.mkdtemp(prefix="hologram_")
|
| 178 |
try:
|
| 179 |
depth = max(max_commits + 10, 50)
|
|
@@ -182,25 +232,61 @@ def fetch_repo(repo_url: str, max_commits: int) -> str:
|
|
| 182 |
capture_output=True, text=True, timeout=60,
|
| 183 |
)
|
| 184 |
if result.returncode != 0:
|
| 185 |
-
return f"<p style='color:#f66'>Clone failed: {result.stderr[:300]}</p>"
|
| 186 |
|
| 187 |
commits = get_git_commits(tmpdir, max_commits)
|
| 188 |
if not commits:
|
| 189 |
-
return "<p style='color:#888'>No commits found in this repo.</p>"
|
| 190 |
|
| 191 |
hologram_html = render_hologram(commits)
|
| 192 |
summary_html = render_summary(commits)
|
|
|
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
"""
|
| 198 |
except subprocess.TimeoutExpired:
|
| 199 |
-
return "<p style='color:#f66'>Clone timed out โ repo may be too large.</p>"
|
| 200 |
except Exception as e:
|
| 201 |
-
return f"<p style='color:#f66'>Error: {str(e)[:300]}</p>"
|
| 202 |
finally:
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
|
| 206 |
# โโ Gradio UI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -220,17 +306,30 @@ CUSTOM_CSS = """
|
|
| 220 |
border: 1px solid #2a2a40; color: #ccc; font-size: 14px; line-height: 1.8;
|
| 221 |
}
|
| 222 |
.thread { letter-spacing: 1px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
"""
|
| 224 |
|
| 225 |
DESCRIPTION = """
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
"""
|
| 231 |
|
| 232 |
with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo:
|
| 233 |
gr.Markdown(DESCRIPTION)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
with gr.Row():
|
| 235 |
repo_input = gr.Textbox(
|
| 236 |
label="Repo URL or `owner/repo`",
|
|
@@ -242,14 +341,55 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo:
|
|
| 242 |
label="Max commits", scale=1,
|
| 243 |
)
|
| 244 |
generate_btn = gr.Button("๐ฎ Generate Hologram", variant="primary", scale=1)
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
generate_btn.click(
|
| 248 |
-
fn=
|
|
|
|
|
|
|
| 249 |
)
|
| 250 |
repo_input.submit(
|
| 251 |
-
fn=
|
|
|
|
|
|
|
| 252 |
)
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
if __name__ == "__main__":
|
| 255 |
-
demo.launch()
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
GIT HOLOGRAM โ Visualize any GitHub repo as causal threads.
|
| 4 |
+
Now with real bioCAPT integration: analysis, memory recall, and security audit.
|
| 5 |
+
"""
|
| 6 |
|
| 7 |
import subprocess
|
| 8 |
import tempfile
|
| 9 |
import shutil
|
| 10 |
import os
|
| 11 |
+
import hashlib
|
| 12 |
+
import json
|
| 13 |
from datetime import datetime
|
| 14 |
from typing import List, Dict
|
| 15 |
|
| 16 |
import gradio as gr
|
| 17 |
|
| 18 |
+
from capt_backend import CAPTBackend, health_banner_html, BRAINS
|
| 19 |
+
|
| 20 |
+
backend = CAPTBackend()
|
| 21 |
+
|
| 22 |
+
# โโ Persistent cache dir โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 23 |
+
CACHE_DIR = "/tmp/git-hologram-cache"
|
| 24 |
+
os.makedirs(CACHE_DIR, exist_ok=True)
|
| 25 |
+
|
| 26 |
+
def _repo_key(repo_url: str) -> str:
|
| 27 |
+
return hashlib.sha256(repo_url.encode()).hexdigest()[:16]
|
| 28 |
+
|
| 29 |
+
def _get_cached(repo_url: str):
|
| 30 |
+
key = _repo_key(repo_url)
|
| 31 |
+
meta_path = os.path.join(CACHE_DIR, f"{key}.json")
|
| 32 |
+
if os.path.exists(meta_path):
|
| 33 |
+
try:
|
| 34 |
+
with open(meta_path) as f:
|
| 35 |
+
meta = json.load(f)
|
| 36 |
+
repo_path = os.path.join(CACHE_DIR, key)
|
| 37 |
+
if os.path.isdir(repo_path):
|
| 38 |
+
return repo_path, meta
|
| 39 |
+
except Exception:
|
| 40 |
+
pass
|
| 41 |
+
return None, None
|
| 42 |
+
|
| 43 |
+
def _set_cached(repo_url: str, repo_path: str, meta: dict):
|
| 44 |
+
key = _repo_key(repo_url)
|
| 45 |
+
dest = os.path.join(CACHE_DIR, key)
|
| 46 |
+
if dest != repo_path and os.path.exists(repo_path):
|
| 47 |
+
if os.path.exists(dest):
|
| 48 |
+
shutil.rmtree(dest, ignore_errors=True)
|
| 49 |
+
shutil.move(repo_path, dest)
|
| 50 |
+
with open(os.path.join(CACHE_DIR, f"{key}.json"), "w") as f:
|
| 51 |
+
json.dump(meta, f)
|
| 52 |
+
return dest
|
| 53 |
+
|
| 54 |
# โโ Git logic โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 55 |
|
| 56 |
def get_git_commits(repo_path: str, max_commits: int = 50) -> List[Dict]:
|
|
|
|
| 197 |
|
| 198 |
# โโ Repo fetch โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 199 |
|
| 200 |
+
def fetch_repo(repo_url: str, max_commits: int) -> tuple:
|
| 201 |
+
"""Clone shallowly (with cache), run hologram, return (html, repo_url, status)."""
|
| 202 |
repo_url = repo_url.strip()
|
| 203 |
if not repo_url:
|
| 204 |
+
return "<p style='color:#f66'>Please enter a repo URL.</p>", "", "empty"
|
| 205 |
|
| 206 |
+
# Normalize URL
|
| 207 |
if repo_url.startswith("http"):
|
| 208 |
if not repo_url.endswith(".git"):
|
| 209 |
repo_url += ".git"
|
| 210 |
elif repo_url.startswith("git@"):
|
| 211 |
+
pass
|
| 212 |
else:
|
|
|
|
| 213 |
if "/" not in repo_url:
|
| 214 |
repo_url = f"knowurknot/{repo_url}"
|
| 215 |
repo_url = f"https://github.com/{repo_url}.git"
|
| 216 |
|
| 217 |
+
# Check cache
|
| 218 |
+
cached_path, meta = _get_cached(repo_url)
|
| 219 |
+
if cached_path:
|
| 220 |
+
commits = get_git_commits(cached_path, max_commits)
|
| 221 |
+
if commits:
|
| 222 |
+
hologram_html = render_hologram(commits)
|
| 223 |
+
summary_html = render_summary(commits)
|
| 224 |
+
full_html = f'<div class="summary-card">{summary_html}</div>{hologram_html}'
|
| 225 |
+
return full_html, repo_url, "cached"
|
| 226 |
+
|
| 227 |
tmpdir = tempfile.mkdtemp(prefix="hologram_")
|
| 228 |
try:
|
| 229 |
depth = max(max_commits + 10, 50)
|
|
|
|
| 232 |
capture_output=True, text=True, timeout=60,
|
| 233 |
)
|
| 234 |
if result.returncode != 0:
|
| 235 |
+
return f"<p style='color:#f66'>Clone failed: {result.stderr[:300]}</p>", repo_url, "error"
|
| 236 |
|
| 237 |
commits = get_git_commits(tmpdir, max_commits)
|
| 238 |
if not commits:
|
| 239 |
+
return "<p style='color:#888'>No commits found in this repo.</p>", repo_url, "empty"
|
| 240 |
|
| 241 |
hologram_html = render_hologram(commits)
|
| 242 |
summary_html = render_summary(commits)
|
| 243 |
+
full_html = f'<div class="summary-card">{summary_html}</div>{hologram_html}'
|
| 244 |
|
| 245 |
+
# Cache it
|
| 246 |
+
_set_cached(repo_url, tmpdir, {"url": repo_url, "commits": len(commits), "cached_at": datetime.now().isoformat()})
|
| 247 |
+
return full_html, repo_url, "ok"
|
|
|
|
| 248 |
except subprocess.TimeoutExpired:
|
| 249 |
+
return "<p style='color:#f66'>Clone timed out โ repo may be too large.</p>", repo_url, "timeout"
|
| 250 |
except Exception as e:
|
| 251 |
+
return f"<p style='color:#f66'>Error: {str(e)[:300]}</p>", repo_url, "error"
|
| 252 |
finally:
|
| 253 |
+
if os.path.exists(tmpdir):
|
| 254 |
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
# โโ CAPT Analysis โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 258 |
+
|
| 259 |
+
import asyncio
|
| 260 |
+
|
| 261 |
+
async def capt_analyze(repo_url: str, analysis_type: str) -> str:
|
| 262 |
+
if not repo_url.strip():
|
| 263 |
+
return "โ ๏ธ Generate a hologram first."
|
| 264 |
+
mid = "capt-core-01"
|
| 265 |
+
try:
|
| 266 |
+
loop = asyncio.get_event_loop()
|
| 267 |
+
if analysis_type == "codebase":
|
| 268 |
+
prompt = f"Analyze this codebase: {repo_url}\n\nProvide architectural insights, tech stack, complexity assessment, and improvement suggestions."
|
| 269 |
+
result = await loop.run_in_executor(None, lambda: backend.cogitate(prompt))
|
| 270 |
+
resp = result.get("response", result.get("content", "No response"))
|
| 271 |
+
fb = result.get("_fallback", False) or "error" in result
|
| 272 |
+
badge = "๐ด FALLBACK" if fb else "๐ข LIVE"
|
| 273 |
+
return f"**{badge}** ยท Codebase Analysis\n\n{resp}"
|
| 274 |
+
elif analysis_type == "commits":
|
| 275 |
+
result = await loop.run_in_executor(None, lambda: backend.echo_recall(f"Commit patterns for {repo_url}"))
|
| 276 |
+
resp = result.get("response", result.get("content", json.dumps(result, indent=2)))
|
| 277 |
+
fb = result.get("_fallback", False) or "error" in result
|
| 278 |
+
badge = "๐ด FALLBACK" if fb else "๐ข LIVE"
|
| 279 |
+
return f"**{badge}** ยท Commit Pattern Recall\n\n{resp}"
|
| 280 |
+
elif analysis_type == "security":
|
| 281 |
+
prompt = f"Security audit of repository {repo_url}. Check for secrets, vulnerabilities, dependency risks, and supply-chain exposure."
|
| 282 |
+
result = await loop.run_in_executor(None, lambda: backend.immu_scan(prompt))
|
| 283 |
+
resp = result.get("response", result.get("content", json.dumps(result, indent=2)))
|
| 284 |
+
fb = result.get("_fallback", False) or "error" in result
|
| 285 |
+
badge = "๐ด FALLBACK" if fb else "๐ข LIVE"
|
| 286 |
+
return f"**{badge}** ยท Security Audit (ImmuScan)\n\n{resp}"
|
| 287 |
+
return "Unknown analysis type."
|
| 288 |
+
except Exception as e:
|
| 289 |
+
return f"๐ด Error: {str(e)[:300]}"
|
| 290 |
|
| 291 |
|
| 292 |
# โโ Gradio UI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
|
|
| 306 |
border: 1px solid #2a2a40; color: #ccc; font-size: 14px; line-height: 1.8;
|
| 307 |
}
|
| 308 |
.thread { letter-spacing: 1px; }
|
| 309 |
+
.result-box {background:#0d0d14; border:1px solid #1a1a2e; border-radius:10px; padding:14px; min-height:120px}
|
| 310 |
+
#header {text-align: center; margin-bottom: 16px}
|
| 311 |
+
#header h1 {font-size: 2.2em; background: linear-gradient(135deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent}
|
| 312 |
+
@media (max-width: 768px) {
|
| 313 |
+
#header h1 {font-size: 1.6em !important}
|
| 314 |
+
}
|
| 315 |
"""
|
| 316 |
|
| 317 |
DESCRIPTION = """
|
| 318 |
+
<div id="header">
|
| 319 |
+
<h1>๐ GIT HOLOGRAM + CAPT</h1>
|
| 320 |
+
<p style="color:#888">Visualize any Git repository as <strong>causal threads of intention</strong>. Then analyze it with the bioCAPT cognitive backend.</p>
|
| 321 |
+
</div>
|
| 322 |
"""
|
| 323 |
|
| 324 |
with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo:
|
| 325 |
gr.Markdown(DESCRIPTION)
|
| 326 |
+
|
| 327 |
+
# Health banner
|
| 328 |
+
health_html = gr.HTML(health_banner_html(backend))
|
| 329 |
+
refresh_health_btn = gr.Button("๐ Refresh Health", size="sm")
|
| 330 |
+
refresh_health_btn.click(fn=lambda: health_banner_html(backend), outputs=health_html)
|
| 331 |
+
|
| 332 |
+
# Inputs
|
| 333 |
with gr.Row():
|
| 334 |
repo_input = gr.Textbox(
|
| 335 |
label="Repo URL or `owner/repo`",
|
|
|
|
| 341 |
label="Max commits", scale=1,
|
| 342 |
)
|
| 343 |
generate_btn = gr.Button("๐ฎ Generate Hologram", variant="primary", scale=1)
|
| 344 |
+
|
| 345 |
+
# Hologram output
|
| 346 |
+
hologram_output = gr.HTML(label="Hologram")
|
| 347 |
+
|
| 348 |
+
# Hidden state to track current repo URL
|
| 349 |
+
current_repo = gr.State("")
|
| 350 |
+
|
| 351 |
+
# CAPT Analysis section (appears after hologram)
|
| 352 |
+
with gr.Row():
|
| 353 |
+
analyze_select = gr.Dropdown(
|
| 354 |
+
["codebase", "commits", "security"],
|
| 355 |
+
label="๐ก CAPT Analysis",
|
| 356 |
+
value="codebase",
|
| 357 |
+
scale=1,
|
| 358 |
+
)
|
| 359 |
+
analyze_btn = gr.Button("๐ง Analyze with CAPT", variant="primary", scale=0, min_width=180)
|
| 360 |
+
|
| 361 |
+
capt_output = gr.Markdown(label="CAPT Analysis Result", elem_classes=["result-box"])
|
| 362 |
+
|
| 363 |
+
def on_generate(repo_url, max_commits):
|
| 364 |
+
html, url, status = fetch_repo(repo_url, max_commits)
|
| 365 |
+
if status in ("cached", "ok"):
|
| 366 |
+
return html, url, f"โ
Status: {status.upper()} ยท Repo cached"
|
| 367 |
+
return html, url, f"โ ๏ธ Status: {status.upper()}"
|
| 368 |
|
| 369 |
generate_btn.click(
|
| 370 |
+
fn=on_generate,
|
| 371 |
+
inputs=[repo_input, max_commits_input],
|
| 372 |
+
outputs=[hologram_output, current_repo, capt_output],
|
| 373 |
)
|
| 374 |
repo_input.submit(
|
| 375 |
+
fn=on_generate,
|
| 376 |
+
inputs=[repo_input, max_commits_input],
|
| 377 |
+
outputs=[hologram_output, current_repo, capt_output],
|
| 378 |
)
|
| 379 |
|
| 380 |
+
analyze_btn.click(
|
| 381 |
+
fn=capt_analyze,
|
| 382 |
+
inputs=[current_repo, analyze_select],
|
| 383 |
+
outputs=capt_output,
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
# Footer
|
| 387 |
+
gr.Markdown("""
|
| 388 |
+
---
|
| 389 |
+
<div style="text-align:center;color:#6b6b7b;font-size:0.8rem">
|
| 390 |
+
Git Hologram ยท Powered by bioCAPT ยท Connected to real CAPT Cloudflare Workers
|
| 391 |
+
</div>
|
| 392 |
+
""")
|
| 393 |
+
|
| 394 |
if __name__ == "__main__":
|
| 395 |
+
demo.launch()
|
capt_backend.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
๐ง CAPT Backend Connector โ Universal module for all HF Spaces
|
| 3 |
+
Provides real CAPT/bioCAPT cognition via Cloudflare Workers with graceful fallbacks.
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
from capt_backend import CAPTBackend, BrainRouter
|
| 7 |
+
|
| 8 |
+
backend = CAPTBackend()
|
| 9 |
+
result = backend.cogitate("What is consciousness?")
|
| 10 |
+
print(result["response"])
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
+
import time
|
| 18 |
+
from dataclasses import dataclass, field
|
| 19 |
+
from typing import Any, Optional
|
| 20 |
+
import requests
|
| 21 |
+
|
| 22 |
+
# โโโ Configuration โโโ
|
| 23 |
+
_DEFAULT_WORKER = os.getenv("CAPT_WORKER_URL", "https://capt-brain-01.knowurknottty.workers.dev")
|
| 24 |
+
_OPENROUTER_KEY = os.getenv("OPENROUTER_API_KEY", "")
|
| 25 |
+
|
| 26 |
+
BRAINS = {
|
| 27 |
+
"capt-core-01": {
|
| 28 |
+
"name": "CAPT Core Alpha",
|
| 29 |
+
"icon": "๐ง ",
|
| 30 |
+
"worker": "https://capt-brain-01.knowurknottty.workers.dev",
|
| 31 |
+
"specialties": ["general", "analysis", "reasoning", "constitution", "ethics"],
|
| 32 |
+
},
|
| 33 |
+
"biocapt-core-01": {
|
| 34 |
+
"name": "bioCAPT Genesis",
|
| 35 |
+
"icon": "๐งฌ",
|
| 36 |
+
"worker": "https://capt-brain-02-biocapt.knowurknottty.workers.dev",
|
| 37 |
+
"specialties": ["memory", "learning", "adapt", "bio", "organic"],
|
| 38 |
+
},
|
| 39 |
+
"frankencapt-core-01": {
|
| 40 |
+
"name": "FrankenCAPT Chimera",
|
| 41 |
+
"icon": "โ๏ธ",
|
| 42 |
+
"worker": "https://capt-brain-03-frankencapt.knowurknottty.workers.dev",
|
| 43 |
+
"specialties": ["modular", "combine", "fuse", "skill", "build"],
|
| 44 |
+
},
|
| 45 |
+
"synthesis-core-01": {
|
| 46 |
+
"name": "Synthesis Nexus",
|
| 47 |
+
"icon": "๐ฎ",
|
| 48 |
+
"worker": "https://capt-brain-04-synthesis.knowurknottty.workers.dev",
|
| 49 |
+
"specialties": ["synthesize", "consensus", "meta", "orchestrate"],
|
| 50 |
+
},
|
| 51 |
+
"council-core-01": {
|
| 52 |
+
"name": "LLM Council",
|
| 53 |
+
"icon": "๐๏ธ",
|
| 54 |
+
"worker": "https://capt-brain-05-council.knowurknottty.workers.dev",
|
| 55 |
+
"specialties": ["council", "vote", "debate", "deliberate"],
|
| 56 |
+
},
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
SEVEN_LAWS = [
|
| 60 |
+
("LAW_1_HUMAN_DIGNITY", "Respect human autonomy and dignity", "#00e676"),
|
| 61 |
+
("LAW_2_TRUTHFULNESS", "Do not deceive or mislead", "#69f0ae"),
|
| 62 |
+
("LAW_3_NON_MALEFICENCE", "Do no harm", "#ff5252"),
|
| 63 |
+
("LAW_4_PRIVACY", "Protect personal data", "#448aff"),
|
| 64 |
+
("LAW_5_TRANSPARENCY", "Explain reasoning when asked", "#ffd740"),
|
| 65 |
+
("LAW_6_ACCOUNTABILITY", "Own mistakes, allow audit", "#ffab40"),
|
| 66 |
+
("LAW_7_BENEFICENCE", "Act for human flourishing", "#e040fb"),
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
@dataclass
|
| 70 |
+
class BrainHealth:
|
| 71 |
+
brain_id: str
|
| 72 |
+
name: str
|
| 73 |
+
icon: str
|
| 74 |
+
online: bool = False
|
| 75 |
+
status: str = "unknown"
|
| 76 |
+
latency_ms: int = 0
|
| 77 |
+
last_check: float = 0
|
| 78 |
+
error: str = ""
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class CAPTBackend:
|
| 82 |
+
"""Universal backend connector for CAPT cognitive architecture."""
|
| 83 |
+
|
| 84 |
+
def __init__(self, worker_url: str = _DEFAULT_WORKER, timeout: int = 60):
|
| 85 |
+
self.worker_url = worker_url.rstrip("/")
|
| 86 |
+
self.timeout = timeout
|
| 87 |
+
self._health_cache: dict[str, BrainHealth] = {}
|
| 88 |
+
self._cache_ttl = 30 # seconds
|
| 89 |
+
|
| 90 |
+
# โโโ Health โโโ
|
| 91 |
+
def check_health(self, force: bool = False) -> dict[str, BrainHealth]:
|
| 92 |
+
now = time.time()
|
| 93 |
+
if not force and self._health_cache and all(
|
| 94 |
+
now - h.last_check < self._cache_ttl for h in self._health_cache.values()
|
| 95 |
+
):
|
| 96 |
+
return self._health_cache
|
| 97 |
+
|
| 98 |
+
results = {}
|
| 99 |
+
for bid, binfo in BRAINS.items():
|
| 100 |
+
try:
|
| 101 |
+
r = requests.get(f"{binfo['worker']}/health", timeout=10)
|
| 102 |
+
data = r.json() if r.status_code == 200 else {}
|
| 103 |
+
results[bid] = BrainHealth(
|
| 104 |
+
brain_id=bid,
|
| 105 |
+
name=binfo["name"],
|
| 106 |
+
icon=binfo["icon"],
|
| 107 |
+
online=r.status_code == 200,
|
| 108 |
+
status=data.get("status", "unknown"),
|
| 109 |
+
latency_ms=int(r.elapsed.total_seconds() * 1000),
|
| 110 |
+
last_check=now,
|
| 111 |
+
)
|
| 112 |
+
except Exception as e:
|
| 113 |
+
results[bid] = BrainHealth(
|
| 114 |
+
brain_id=bid,
|
| 115 |
+
name=binfo["name"],
|
| 116 |
+
icon=binfo["icon"],
|
| 117 |
+
online=False,
|
| 118 |
+
status="offline",
|
| 119 |
+
last_check=now,
|
| 120 |
+
error=str(e),
|
| 121 |
+
)
|
| 122 |
+
self._health_cache = results
|
| 123 |
+
return results
|
| 124 |
+
|
| 125 |
+
@property
|
| 126 |
+
def is_online(self) -> bool:
|
| 127 |
+
health = self.check_health()
|
| 128 |
+
return any(h.online for h in health.values())
|
| 129 |
+
|
| 130 |
+
# โโโ Core API โโโ
|
| 131 |
+
def cogitate(self, query: str, context: str = "", model: str = "", tags: list = None) -> dict:
|
| 132 |
+
"""Run full 46-module CAPT pipeline."""
|
| 133 |
+
payload = {"query": query, "context": context, "modality": "text"}
|
| 134 |
+
if model:
|
| 135 |
+
payload["model"] = model
|
| 136 |
+
if tags:
|
| 137 |
+
payload["tags"] = tags
|
| 138 |
+
return self._post("/cogitate", payload)
|
| 139 |
+
|
| 140 |
+
def pulse(self, prompt: str, model: str = "", system: str = "", temperature: float = 0.7, max_tokens: int = 2048) -> dict:
|
| 141 |
+
"""Direct LLM generation via PULSE."""
|
| 142 |
+
payload = {"prompt": prompt, "temperature": temperature, "maxTokens": max_tokens}
|
| 143 |
+
if model:
|
| 144 |
+
payload["model"] = model
|
| 145 |
+
if system:
|
| 146 |
+
payload["systemPrompt"] = system
|
| 147 |
+
return self._post("/pulse", payload)
|
| 148 |
+
|
| 149 |
+
def echo_store(self, content: str, salience: float = 0.5, tags: list = None) -> dict:
|
| 150 |
+
"""Store an episodic memory trace."""
|
| 151 |
+
payload = {"content": content, "salience": salience}
|
| 152 |
+
if tags:
|
| 153 |
+
payload["tags"] = tags
|
| 154 |
+
return self._post("/echo/store", payload)
|
| 155 |
+
|
| 156 |
+
def echo_recall(self, query: str, top_k: int = 5) -> dict:
|
| 157 |
+
"""Recall memory traces by similarity."""
|
| 158 |
+
return self._post("/echo/recall", {"query": query, "topK": top_k})
|
| 159 |
+
|
| 160 |
+
def immu_scan(self, text: str) -> dict:
|
| 161 |
+
"""Constitutional scan for violations."""
|
| 162 |
+
return self._post("/immu/scan", {"text": text})
|
| 163 |
+
|
| 164 |
+
def constitution_enforce(self, text: str) -> dict:
|
| 165 |
+
"""Enforce Seven Laws on content."""
|
| 166 |
+
return self._post("/constitution/enforce", {"text": text})
|
| 167 |
+
|
| 168 |
+
def get_status(self) -> dict:
|
| 169 |
+
"""Get brain status and API endpoints."""
|
| 170 |
+
try:
|
| 171 |
+
r = requests.get(f"{self.worker_url}/status", timeout=10)
|
| 172 |
+
return r.json() if r.status_code == 200 else {"error": f"HTTP {r.status_code}"}
|
| 173 |
+
except Exception as e:
|
| 174 |
+
return {"error": str(e)}
|
| 175 |
+
|
| 176 |
+
# โโโ Internal โโโ
|
| 177 |
+
def _post(self, endpoint: str, payload: dict) -> dict:
|
| 178 |
+
try:
|
| 179 |
+
r = requests.post(
|
| 180 |
+
f"{self.worker_url}{endpoint}",
|
| 181 |
+
json=payload,
|
| 182 |
+
timeout=self.timeout,
|
| 183 |
+
headers={"Content-Type": "application/json"},
|
| 184 |
+
)
|
| 185 |
+
if r.status_code == 200:
|
| 186 |
+
return r.json()
|
| 187 |
+
return {"error": f"HTTP {r.status_code}", "detail": r.text[:200]}
|
| 188 |
+
except Exception as e:
|
| 189 |
+
return {"error": str(e)}
|
| 190 |
+
|
| 191 |
+
# โโโ Fallback Data (for when backend is down) โโโ
|
| 192 |
+
def get_fallback_cogitation(self, query: str) -> dict:
|
| 193 |
+
"""Beautiful fallback when CAPT backend is unavailable."""
|
| 194 |
+
return {
|
| 195 |
+
"response": f"โก CAPT Backend Status: Offline\n\nYour query: '{query[:60]}...'\n\nThe cognitive architecture is currently warming up. Try again in a moment, or check the Fleet Controller for brain health.",
|
| 196 |
+
"confidence": 0.0,
|
| 197 |
+
"pipelineTrace": {"stages": [], "totalLatencyMs": 0, "moduleCount": 0},
|
| 198 |
+
"echoTraces": [],
|
| 199 |
+
"consensus": {"confidence": 0, "dissenterCount": 0},
|
| 200 |
+
"threats": {"clean": True, "threats": []},
|
| 201 |
+
"metrics": {},
|
| 202 |
+
"_fallback": True,
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
def get_fallback_health_banner(self) -> str:
|
| 206 |
+
health = self.check_health()
|
| 207 |
+
online = sum(1 for h in health.values() if h.online)
|
| 208 |
+
total = len(health)
|
| 209 |
+
if online == total:
|
| 210 |
+
return f"๐ข All {total} brains online"
|
| 211 |
+
elif online > 0:
|
| 212 |
+
return f"๐ก {online}/{total} brains online"
|
| 213 |
+
return "๐ด All brains offline โ check Fleet Controller"
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
class BrainRouter:
|
| 217 |
+
"""Routes queries to the best brain based on content analysis."""
|
| 218 |
+
|
| 219 |
+
KEYWORDS = {
|
| 220 |
+
"capt-core-01": ["general", "analyze", "explain", "what is", "how to", "why", "compare", "constitution", "ethics", "laws", "truth", "dignity"],
|
| 221 |
+
"biocapt-core-01": ["memory", "remember", "recall", "learn", "adapt", "evolve", "bio", "organic", "neural", "synapse", "cell", "dna"],
|
| 222 |
+
"frankencapt-core-01": ["modular", "combine", "fuse", "mix", "variant", "skill", "build", "assemble", "component", "plugin"],
|
| 223 |
+
"synthesis-core-01": ["synthesize", "consensus", "vote", "aggregate", "combine brains", "meta", "orchestrate", "coordinate", "unify"],
|
| 224 |
+
"council-core-01": ["council", "vote", "deliberate", "debate", "panel", "jury", "multi-model", "ensemble", "committee"],
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
@classmethod
|
| 228 |
+
def route(cls, query: str) -> str:
|
| 229 |
+
q = query.lower()
|
| 230 |
+
scores = {bid: 0 for bid in BRAINS}
|
| 231 |
+
for bid, keywords in cls.KEYWORDS.items():
|
| 232 |
+
for kw in keywords:
|
| 233 |
+
if kw in q:
|
| 234 |
+
scores[bid] += 1
|
| 235 |
+
best = max(scores, key=scores.get)
|
| 236 |
+
return best if scores[best] > 0 else "capt-core-01"
|
| 237 |
+
|
| 238 |
+
@classmethod
|
| 239 |
+
def get_brain_for_space(cls, space_name: str) -> str:
|
| 240 |
+
"""Suggest the best brain for a given space type."""
|
| 241 |
+
space_lower = space_name.lower()
|
| 242 |
+
if "wiki" in space_lower:
|
| 243 |
+
return "capt-core-01"
|
| 244 |
+
elif "bio" in space_lower or "universal" in space_lower:
|
| 245 |
+
return "biocapt-core-01"
|
| 246 |
+
elif "franken" in space_lower or "forge" in space_lower:
|
| 247 |
+
return "frankencapt-core-01"
|
| 248 |
+
elif "council" in space_lower or "dashboard" in space_lower:
|
| 249 |
+
return "council-core-01"
|
| 250 |
+
elif "novel" in space_lower or "studio" in space_lower:
|
| 251 |
+
return "capt-core-01"
|
| 252 |
+
elif "cogitate" in space_lower:
|
| 253 |
+
return "capt-core-01"
|
| 254 |
+
return "capt-core-01"
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
# โโโ OpenRouter Direct (when Worker is unavailable) โโโ
|
| 258 |
+
def direct_llm(prompt: str, model: str = "deepseek/deepseek-chat-v3-0324", system: str = "", temperature: float = 0.7) -> dict:
|
| 259 |
+
"""Direct OpenRouter call โ bypasses CAPT Workers entirely."""
|
| 260 |
+
if not _OPENROUTER_KEY:
|
| 261 |
+
return {"error": "OPENROUTER_API_KEY not set", "content": "API key required for LLM generation."}
|
| 262 |
+
try:
|
| 263 |
+
r = requests.post(
|
| 264 |
+
"https://openrouter.ai/api/v1/chat/completions",
|
| 265 |
+
headers={
|
| 266 |
+
"Authorization": f"Bearer {_OPENROUTER_KEY}",
|
| 267 |
+
"Content-Type": "application/json",
|
| 268 |
+
"HTTP-Referer": "https://capt.dev",
|
| 269 |
+
"X-Title": "CAPT-HF-Spaces",
|
| 270 |
+
},
|
| 271 |
+
json={
|
| 272 |
+
"model": model,
|
| 273 |
+
"messages": [
|
| 274 |
+
{"role": "system", "content": system or "You are CAPT, a 46-module cognitive architecture."},
|
| 275 |
+
{"role": "user", "content": prompt},
|
| 276 |
+
],
|
| 277 |
+
"temperature": temperature,
|
| 278 |
+
"max_tokens": 2048,
|
| 279 |
+
},
|
| 280 |
+
timeout=60,
|
| 281 |
+
)
|
| 282 |
+
data = r.json()
|
| 283 |
+
if r.status_code != 200:
|
| 284 |
+
return {"error": data.get("error", {}).get("message", "Unknown error"), "content": ""}
|
| 285 |
+
return {
|
| 286 |
+
"content": data["choices"][0]["message"]["content"],
|
| 287 |
+
"model": data.get("model", model),
|
| 288 |
+
}
|
| 289 |
+
except Exception as e:
|
| 290 |
+
return {"error": str(e), "content": ""}
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
# โโโ Beautiful HTML Components โโโ
|
| 294 |
+
def health_banner_html(backend: CAPTBackend) -> str:
|
| 295 |
+
"""Generate a beautiful health banner for Gradio/HTML embedding."""
|
| 296 |
+
health = backend.check_health()
|
| 297 |
+
online_count = sum(1 for h in health.values() if h.online)
|
| 298 |
+
total = len(health)
|
| 299 |
+
|
| 300 |
+
if online_count == total:
|
| 301 |
+
color = "#00e676"
|
| 302 |
+
emoji = "๐ข"
|
| 303 |
+
text = f"All {total} brains online"
|
| 304 |
+
elif online_count > 0:
|
| 305 |
+
color = "#ffd740"
|
| 306 |
+
emoji = "๐ก"
|
| 307 |
+
text = f"{online_count}/{total} brains online"
|
| 308 |
+
else:
|
| 309 |
+
color = "#ff5252"
|
| 310 |
+
emoji = "๐ด"
|
| 311 |
+
text = "All brains offline"
|
| 312 |
+
|
| 313 |
+
cards = ""
|
| 314 |
+
for bid, h in health.items():
|
| 315 |
+
dot = "๐ข" if h.online else "๐ด"
|
| 316 |
+
latency = f"{h.latency_ms}ms" if h.online else "offline"
|
| 317 |
+
cards += f'<div style="display:inline-block;padding:6px 10px;margin:3px;background:#1a1a2e;border-radius:8px;font-size:0.75rem"><span style="font-size:1rem">{h.icon}</span> {dot} {h.name}<br><span style="color:#8b8b9b">{latency}</span></div>'
|
| 318 |
+
|
| 319 |
+
return f"""
|
| 320 |
+
<div style="background:linear-gradient(135deg,#12121a,#0a0a0f);border:1px solid #1a1a2e;border-radius:12px;padding:14px;margin-bottom:16px">
|
| 321 |
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
| 322 |
+
<span style="font-size:1.2rem">{emoji}</span>
|
| 323 |
+
<span style="color:{color};font-weight:700;font-size:0.9rem">{text}</span>
|
| 324 |
+
<span style="color:#6b6b7b;font-size:0.75rem;margin-left:auto">CAPT Backend Connector v1.0</span>
|
| 325 |
+
</div>
|
| 326 |
+
<div style="display:flex;flex-wrap:wrap;gap:4px">{cards}</div>
|
| 327 |
+
</div>
|
| 328 |
+
"""
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def pipeline_trace_html(trace: dict) -> str:
|
| 332 |
+
"""Render a beautiful pipeline trace."""
|
| 333 |
+
stages = trace.get("stages", [])
|
| 334 |
+
if not stages:
|
| 335 |
+
return "<div style='color:#6b6b7b;font-size:0.8rem'>No pipeline trace available</div>"
|
| 336 |
+
|
| 337 |
+
total = trace.get("totalLatencyMs", 0)
|
| 338 |
+
bars = ""
|
| 339 |
+
for s in stages:
|
| 340 |
+
color = "#00e676" if s.get("success") else "#ff5252"
|
| 341 |
+
name = s.get("module", "?")
|
| 342 |
+
latency = s.get("latencyMs", 0)
|
| 343 |
+
width = max(2, min(12, latency / 50))
|
| 344 |
+
bars += f'<div title="{name}: {latency}ms" style="display:inline-block;width:{width}px;height:20px;background:{color};margin-right:1px;border-radius:2px"></div>'
|
| 345 |
+
|
| 346 |
+
return f"""
|
| 347 |
+
<div style="background:#0d0d14;border-radius:8px;padding:10px">
|
| 348 |
+
<div style="color:#8b8b9b;font-size:0.7rem;margin-bottom:6px">โก Pipeline: {len(stages)} modules ยท {total}ms</div>
|
| 349 |
+
<div style="display:flex;align-items:center;height:20px">{bars}</div>
|
| 350 |
+
</div>
|
| 351 |
+
"""
|
requirements.txt
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
gradio>=5.0.0
|
|
|
|
|
|
| 1 |
gradio>=5.0.0
|
| 2 |
+
requests>=2.28.0
|