Spaces:
Paused
Paused
| """ | |
| ForensicAI 2nd Brain β Hugging Face Space | |
| Interactive morphic simulation UI + GPT Action API endpoints. | |
| """ | |
| from __future__ import annotations | |
| import base64, hashlib, os | |
| from pathlib import Path | |
| from typing import Optional | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import HTMLResponse, Response, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from pydantic import BaseModel, Field | |
| import brain as brain_store | |
| from simulation import generate_forensic_image, generate_case_heatmap, CASE_TYPES | |
| SIMS_DIR = Path("/tmp/forensic_sims") | |
| SIMS_DIR.mkdir(parents=True, exist_ok=True) | |
| app = FastAPI(title="ForensicAI 2nd Brain", version="1.0.0") | |
| app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) | |
| app.mount("/sims", StaticFiles(directory=str(SIMS_DIR)), name="sims") | |
| # ββ Interactive UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| HTML = """<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <title>ForensicAI Β· 2nd Brain</title> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{background:#05050f;color:#c0ccdd;font-family:'Courier New',monospace;overflow:hidden;height:100vh;display:flex;flex-direction:column} | |
| #hdr{display:flex;align-items:center;justify-content:space-between;padding:9px 16px;background:rgba(5,5,20,.95);border-bottom:1px solid #0a2a4a} | |
| #title{color:#00b4d8;font-size:13px;letter-spacing:3px;font-weight:bold} | |
| #gen{color:#9040c8;font-size:11px;letter-spacing:2px} | |
| #main{display:flex;flex:1;overflow:hidden} | |
| #cvs-wrap{flex:1;position:relative} | |
| canvas{width:100%;height:100%;display:block;image-rendering:pixelated} | |
| #side{width:255px;background:rgba(5,10,25,.97);border-left:1px solid #0a2a4a;overflow-y:auto;display:flex;flex-direction:column} | |
| .pnl{padding:12px;border-bottom:1px solid #0d1e35} | |
| .ph{color:#1e6fa0;font-size:9px;letter-spacing:2px;margin-bottom:9px;text-transform:uppercase} | |
| .st{display:flex;justify-content:space-between;font-size:10px;margin:5px 0} | |
| .sv{color:#00b4d8;font-weight:bold} | |
| .bw{margin:4px 0}.bl{font-size:9px;color:#6080a0;margin-bottom:2px} | |
| .bt{height:5px;background:#0d1e35;border-radius:2px;overflow:hidden} | |
| .bf{height:100%;border-radius:2px;transition:width .7s} | |
| #ins{flex:1;padding:12px;overflow-y:auto} | |
| .ins{font-size:9px;color:#405060;margin:3px 0;padding:3px 6px;border-left:2px solid #0d1e35} | |
| .ins.n{color:#7090a8;border-left-color:#00b4d8} | |
| #ftr{padding:7px 16px;background:rgba(5,5,20,.95);border-top:1px solid #0a2a4a;font-size:9px;color:#253545;display:flex;justify-content:space-between} | |
| .dot{width:5px;height:5px;border-radius:50%;background:#00b4d8;display:inline-block;animation:p 1.6s infinite} | |
| @keyframes p{0%,100%{opacity:1}50%{opacity:.2}} | |
| </style> | |
| </head> | |
| <body> | |
| <div id="hdr"> | |
| <span id="title">⬑ FORENSICAI · 2ND BRAIN</span> | |
| <span id="gen">GENERATION <span id="gn">001</span></span> | |
| </div> | |
| <div id="main"> | |
| <div id="cvs-wrap"><canvas id="c"></canvas></div> | |
| <div id="side"> | |
| <div class="pnl"> | |
| <div class="ph">BRAIN STATE</div> | |
| <div class="st"><span>SESSIONS</span><span class="sv" id="ss">0</span></div> | |
| <div class="st"><span>ANALYSES</span><span class="sv" id="sa">0</span></div> | |
| <div class="st"><span>EVIDENCE ITEMS</span><span class="sv" id="se">0</span></div> | |
| <div class="st"><span>AVG CONFIDENCE</span><span class="sv" id="sc">0.70</span></div> | |
| <div class="st"><span>DOMINANT CASE</span><span class="sv" id="sd">β</span></div> | |
| </div> | |
| <div class="pnl"> | |
| <div class="ph">CASE ACTIVITY</div> | |
| <div id="cbars"></div> | |
| </div> | |
| <div class="pnl"> | |
| <div class="ph">SIM PARAMETERS</div> | |
| <div class="st"><span>FEED RATE</span><span class="sv" id="sf">0.0370</span></div> | |
| <div class="st"><span>KILL RATE</span><span class="sv" id="sk">0.0600</span></div> | |
| <div class="st"><span>GRID</span><span class="sv">120 Γ 120</span></div> | |
| </div> | |
| <div id="ins"> | |
| <div class="ph" style="margin-bottom:7px">CASE INTELLIGENCE LOG</div> | |
| <div id="il"><div class="ins">Awaiting dataβ¦</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="ftr"> | |
| <span><span class="dot"></span> MORPHIC ENGINE ACTIVE</span> | |
| <span id="fp">f=0.0370 k=0.0600</span> | |
| </div> | |
| <script> | |
| const W=120,H=120,Du=0.16,Dv=0.08; | |
| let simF=0.037,simK=0.060; | |
| const PALETTES={ | |
| homicide: [[210,40,40],[120,40,200],[230,240,255]], | |
| sexual_assault: [[160,60,220],[0,180,220],[230,240,255]], | |
| digital_crime: [[0,180,220],[30,80,200],[230,240,255]], | |
| drug_offense: [[245,160,10],[20,200,100],[230,240,255]], | |
| arson: [[245,100,20],[210,40,40],[245,200,180]], | |
| burglary: [[80,140,255],[245,160,10],[230,240,255]], | |
| fraud: [[20,200,100],[0,180,220],[230,240,255]], | |
| hit_and_run: [[245,130,50],[210,40,40],[0,180,220]], | |
| default: [[0,180,220],[30,80,200],[230,240,255]], | |
| }; | |
| let pal=PALETTES.default; | |
| const CASE_COLORS={ | |
| homicide:'#d22828',sexual_assault:'#9040c8',digital_crime:'#00b4d8', | |
| drug_offense:'#f5a00a',arson:'#f56414',burglary:'#5090ff', | |
| fraud:'#14c864',hit_and_run:'#f58232' | |
| }; | |
| function lerp(a,b,t){return[a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t];} | |
| function mapCol(v){ | |
| const blk=[5,5,15],nv=[10,15,40]; | |
| const stops=[[0,blk],[0.18,nv],[0.35,lerp(nv,pal[0],0.5)],[0.55,pal[0]],[0.75,pal[1]],[0.9,pal[2]],[1,blk.map(x=>x+225)]]; | |
| for(let i=0;i<stops.length-1;i++){ | |
| const[t0,c0]=stops[i],[t1,c1]=stops[i+1]; | |
| if(v>=t0&&v<=t1){const t=(v-t0)/(t1-t0);return lerp(c0,c1,t);} | |
| } | |
| return[230,240,255]; | |
| } | |
| let u=new Float32Array(W*H).fill(1),v=new Float32Array(W*H).fill(0); | |
| let nu=new Float32Array(W*H),nv=new Float32Array(W*H); | |
| function seed(n=3){ | |
| for(let i=0;i<n;i++){ | |
| const cx=6+Math.floor(Math.random()*(W-12)),cy=6+Math.floor(Math.random()*(H-12)),r=7; | |
| for(let dy=-r;dy<=r;dy++){for(let dx=-r;dx<=r;dx++){ | |
| const x=(cx+dx+W)%W,y=(cy+dy+H)%H,idx=y*W+x; | |
| u[idx]=0.5+0.02*(Math.random()-.5);v[idx]=0.25+0.02*(Math.random()-.5); | |
| }} | |
| } | |
| } | |
| seed(); | |
| function step(n=6){ | |
| for(let s=0;s<n;s++){ | |
| for(let y=0;y<H;y++){for(let x=0;x<W;x++){ | |
| const i=y*W+x,iu=((y-1+H)%H)*W+x,id=((y+1)%H)*W+x,il=y*W+(x-1+W)%W,ir=y*W+(x+1)%W; | |
| const lu=u[iu]+u[id]+u[il]+u[ir]-4*u[i],lv=v[iu]+v[id]+v[il]+v[ir]-4*v[i]; | |
| const uvv=u[i]*v[i]*v[i]; | |
| nu[i]=Math.max(0,Math.min(1,u[i]+Du*lu-uvv+simF*(1-u[i]))); | |
| nv[i]=Math.max(0,Math.min(1,v[i]+Dv*lv+uvv-(simF+simK)*v[i])); | |
| }} | |
| u.set(nu);v.set(nv); | |
| } | |
| } | |
| const cvs=document.getElementById('c'),ctx=cvs.getContext('2d'); | |
| let off=null,offCtx=null; | |
| function initCvs(){cvs.width=W;cvs.height=H;off=document.createElement('canvas');off.width=W;off.height=H;offCtx=off.getContext('2d');} | |
| function draw(){ | |
| const img=offCtx.createImageData(W,H);const d=img.data; | |
| let mn=1,mx=0;for(let i=0;i<W*H;i++){if(v[i]<mn)mn=v[i];if(v[i]>mx)mx=v[i];} | |
| const rng=mx-mn+1e-9; | |
| for(let i=0;i<W*H;i++){let t=(v[i]-mn)/rng;t=Math.max(0,Math.min(1,t*1.8-0.08));const[r,g,b]=mapCol(t);d[i*4]=r;d[i*4+1]=g;d[i*4+2]=b;d[i*4+3]=255;} | |
| offCtx.putImageData(img,0,0); | |
| ctx.save();ctx.imageSmoothingEnabled=false;ctx.drawImage(off,0,0,cvs.width,cvs.height);ctx.restore(); | |
| } | |
| function applyBrain(b){ | |
| document.getElementById('gn').textContent=String(b.pattern_generation).padStart(3,'0'); | |
| document.getElementById('ss').textContent=b.session_count; | |
| document.getElementById('sa').textContent=b.total_analyses; | |
| document.getElementById('se').textContent=b.total_evidence_items; | |
| document.getElementById('sc').textContent=b.avg_confidence.toFixed(3); | |
| document.getElementById('sd').textContent=b.dominant_case==='default'?'β':b.dominant_case; | |
| document.getElementById('sf').textContent=b.feed_rate.toFixed(5); | |
| document.getElementById('sk').textContent=b.kill_rate.toFixed(5); | |
| document.getElementById('fp').textContent='f='+b.feed_rate.toFixed(5)+' k='+b.kill_rate.toFixed(5); | |
| simF=b.feed_rate;simK=b.kill_rate; | |
| pal=PALETTES[b.dominant_case]||PALETTES.default; | |
| const cf=b.case_frequency,mx=Math.max(...Object.values(cf),1); | |
| document.getElementById('cbars').innerHTML=Object.entries(cf).map(([c,n])=>` | |
| <div class="bw"><div class="bl">${c} <span style="float:right">${n}</span></div> | |
| <div class="bt"><div class="bf" style="width:${Math.round(n/mx*100)}%;background:${CASE_COLORS[c]||'#5090ff'}"></div></div></div>`).join(''); | |
| const ins=b.insights.slice(-8).reverse(); | |
| if(ins.length)document.getElementById('il').innerHTML=ins.map((x,i)=>`<div class="ins${i===0?' n':''}">> ${x.text}</div>`).join(''); | |
| } | |
| async function poll(){try{const r=await fetch('/brain');if(r.ok)applyBrain(await r.json());}catch(_){}} | |
| function resize(){const p=document.getElementById('cvs-wrap');cvs.width=p.clientWidth;cvs.height=p.clientHeight;} | |
| initCvs();resize();window.addEventListener('resize',resize);poll();setInterval(poll,10000); | |
| let last=0; | |
| function loop(ts){if(ts-last>33){last=ts;step(6);draw();}requestAnimationFrame(loop);} | |
| requestAnimationFrame(loop); | |
| </script> | |
| </body> | |
| </html>""" | |
| # ββ Models ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class SimulateRequest(BaseModel): | |
| dominant_case: str = Field("default") | |
| evidence_confidence: float = Field(0.70, ge=0.0, le=1.0) | |
| case_complexity: float = Field(0.50, ge=0.0, le=1.0) | |
| out_size: int = Field(512, ge=128, le=1024) | |
| class BrainUpdateRequest(BaseModel): | |
| case_type: Optional[str] = None | |
| confidence: Optional[float] = None | |
| complexity: Optional[float] = None | |
| n_evidence: int = Field(0, ge=0) | |
| insight: Optional[str] = None | |
| # ββ Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def index(): | |
| return HTML | |
| def health(): | |
| return {"status": "ok", "service": "forensicai-2nd-brain"} | |
| def get_brain(): | |
| return JSONResponse(brain_store.load()) | |
| def update_brain(body: BrainUpdateRequest): | |
| state = brain_store.update( | |
| case_type=body.case_type, | |
| confidence=body.confidence, | |
| complexity=body.complexity, | |
| n_evidence=body.n_evidence, | |
| insight=body.insight, | |
| ) | |
| return JSONResponse(state) | |
| def simulate(body: SimulateRequest): | |
| state = brain_store.load() | |
| png = generate_forensic_image( | |
| dominant_case=body.dominant_case if body.dominant_case != "default" else state["dominant_case"], | |
| evidence_confidence=body.evidence_confidence, | |
| session_count=state["session_count"], | |
| pattern_generation=state["pattern_generation"], | |
| case_complexity=body.case_complexity, | |
| out_size=body.out_size, | |
| ) | |
| return { | |
| "image_b64": base64.b64encode(png).decode(), | |
| "mime": "image/png", | |
| "pattern_generation": state["pattern_generation"], | |
| "feed_rate": state["feed_rate"], | |
| "kill_rate": state["kill_rate"], | |
| "dominant_case": state["dominant_case"], | |
| "display_hint": "Render image_b64 as: ", | |
| } | |
| def brain_visualize_b64(): | |
| state = brain_store.load() | |
| png = generate_case_heatmap( | |
| case_history=[], | |
| confidence_history=state["confidence_history"], | |
| case_frequency=state["case_frequency"], | |
| out_size=512, | |
| ) | |
| return { | |
| "image_b64": base64.b64encode(png).decode(), | |
| "mime": "image/png", | |
| "display_hint": "Render as: ", | |
| } | |
| def reset_brain(): | |
| return JSONResponse(brain_store.reset()) | |