Upload 2 files
Browse files- htmlClaw.html +1676 -0
- hybrid_rag_lib.js +316 -0
htmlClaw.html
ADDED
|
@@ -0,0 +1,1676 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>β‘ Chronos WebLLM Agent</title>
|
| 7 |
+
<script src="/coi-serviceworker.min.js"></script>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;700;900&family=VT323&display=swap" rel="stylesheet">
|
| 10 |
+
<style>
|
| 11 |
+
:root{--bg:#080b08;--bg2:#0c100c;--bg3:#111611;--bg4:#161e16;--fg:#3dff70;--fg-dim:#1c8038;--fg-faint:#0b2e16;--amber:#ffbb33;--cyan:#33f0ff;--red:#ff3355;--border:#1a2e1a;--glow:0 0 8px #3dff7066,0 0 24px #3dff7022;--scan:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,.15) 2px,rgba(0,0,0,.15) 4px)}
|
| 12 |
+
*{box-sizing:border-box;margin:0;padding:0}
|
| 13 |
+
html,body{height:100%;background:var(--bg);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:13px;overflow:hidden}
|
| 14 |
+
body::before{content:'';position:fixed;inset:0;background:var(--scan);pointer-events:none;z-index:9999;opacity:.3}
|
| 15 |
+
body::after{content:'';position:fixed;inset:0;background:radial-gradient(ellipse at center,transparent 55%,#000b 100%);pointer-events:none;z-index:9998}
|
| 16 |
+
|
| 17 |
+
#setup-banner{position:fixed;inset:0;background:#000000ee;z-index:99999;display:flex;align-items:center;justify-content:center;padding:20px}
|
| 18 |
+
#setup-banner.hidden{display:none}
|
| 19 |
+
.sbox{background:var(--bg2);border:1px solid var(--amber);max-width:580px;width:100%;font-size:12px;line-height:1.7}
|
| 20 |
+
.sbox h1{font-family:'Orbitron',sans-serif;font-size:13px;color:var(--amber);padding:14px 18px;border-bottom:1px solid var(--border);letter-spacing:2px}
|
| 21 |
+
.sbox .bd{padding:16px 18px;color:var(--fg-dim)}
|
| 22 |
+
.sbox code{color:var(--cyan);background:var(--bg3);padding:1px 5px}
|
| 23 |
+
.cmd-row{background:var(--bg3);border:1px solid var(--border);padding:10px 14px;margin:8px 0;color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:12px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}
|
| 24 |
+
.cmd-row:hover{border-color:var(--fg-dim)}
|
| 25 |
+
.copy-hint{font-size:10px;color:var(--fg-faint)}
|
| 26 |
+
.sbtn-main{width:100%;background:var(--fg-faint);border:none;border-top:1px solid var(--border);color:var(--fg);font-family:'Orbitron',sans-serif;font-size:10px;letter-spacing:2px;padding:12px;cursor:pointer;transition:all .2s}
|
| 27 |
+
.sbtn-main:hover{background:var(--fg);color:var(--bg)}
|
| 28 |
+
|
| 29 |
+
/* LAYOUT β input row inside chat col, 2-row grid */
|
| 30 |
+
#app{display:grid;grid-template-rows:44px 1fr;grid-template-columns:300px 1fr 240px;height:100vh;gap:1px;background:var(--border)}
|
| 31 |
+
@media(max-width:1100px){#app{grid-template-columns:240px 1fr}#rpanel{display:none}}
|
| 32 |
+
@media(max-width:768px){#app{grid-template-columns:1fr}#sidebar,#rpanel{display:none}}
|
| 33 |
+
|
| 34 |
+
#hdr{grid-column:1/-1;background:var(--bg2);display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--border)}
|
| 35 |
+
.logo{font-family:'Orbitron',sans-serif;font-weight:900;font-size:16px;color:var(--fg);text-shadow:var(--glow);letter-spacing:2px;display:flex;align-items:center;gap:8px}
|
| 36 |
+
.shrimp{font-size:20px;animation:bob 3s ease-in-out infinite}
|
| 37 |
+
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
|
| 38 |
+
.hpills{display:flex;gap:8px;align-items:center}
|
| 39 |
+
.hpill{font-size:9px;letter-spacing:1px;padding:3px 8px;border:1px solid var(--border);color:var(--fg-dim);font-family:'Orbitron',sans-serif}
|
| 40 |
+
.dot{width:7px;height:7px;border-radius:50%;background:var(--red);box-shadow:0 0 5px var(--red);display:inline-block;margin-right:5px;transition:all .3s}
|
| 41 |
+
.dot.ready{background:var(--fg);box-shadow:0 0 6px var(--fg);animation:pulse 2s infinite}
|
| 42 |
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
| 43 |
+
|
| 44 |
+
#sidebar{background:var(--bg2);display:flex;flex-direction:column;overflow-y:auto;overflow-x:hidden}
|
| 45 |
+
#sidebar::-webkit-scrollbar{width:2px}
|
| 46 |
+
#sidebar::-webkit-scrollbar-thumb{background:var(--fg-dim)}
|
| 47 |
+
.stitle{font-family:'Orbitron',sans-serif;font-size:8px;letter-spacing:3px;color:var(--fg-dim);padding:7px 10px 5px;border-bottom:1px solid var(--border);text-transform:uppercase;background:var(--bg3);flex-shrink:0}
|
| 48 |
+
.sbody{padding:8px 10px;border-bottom:1px solid var(--border)}
|
| 49 |
+
select.ctrl{width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:10px;padding:5px 7px;outline:none;cursor:pointer;appearance:none;margin-bottom:4px}
|
| 50 |
+
select.ctrl option{background:var(--bg2)}
|
| 51 |
+
.btn{width:100%;background:transparent;border:1px solid var(--fg-dim);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:11px;padding:6px;cursor:pointer;letter-spacing:1px;transition:all .2s;margin-top:3px}
|
| 52 |
+
.btn:hover:not(:disabled){background:var(--fg-faint);border-color:var(--fg);box-shadow:var(--glow)}
|
| 53 |
+
.btn:disabled{opacity:.35;cursor:not-allowed}
|
| 54 |
+
.bxs{background:transparent;border:1px solid var(--border);color:var(--fg-dim);font-family:'Share Tech Mono',monospace;font-size:9px;padding:2px 7px;cursor:pointer;transition:all .2s}
|
| 55 |
+
.bxs:hover{border-color:var(--fg-dim);color:var(--fg)}
|
| 56 |
+
.meta{font-size:9px;color:var(--fg-dim);margin:3px 0}
|
| 57 |
+
|
| 58 |
+
#prog{margin-top:5px;display:none}
|
| 59 |
+
#prog.vis{display:block}
|
| 60 |
+
#ptrack{width:100%;height:3px;background:var(--bg3);border:1px solid var(--border)}
|
| 61 |
+
#pfill{height:100%;background:var(--fg);width:0%;transition:width .3s;box-shadow:var(--glow)}
|
| 62 |
+
#ptxt{font-size:9px;color:var(--fg-dim);margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 63 |
+
|
| 64 |
+
.frow{display:flex;align-items:center;gap:5px;padding:3px 0;font-size:11px}
|
| 65 |
+
.fname{color:var(--cyan);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
| 66 |
+
.fname.ok{color:var(--fg)}
|
| 67 |
+
.fbadge{font-size:9px;color:var(--fg-dim);min-width:44px;text-align:right}
|
| 68 |
+
input[type=file]{display:none}
|
| 69 |
+
.chips{display:flex;flex-wrap:wrap;gap:3px;margin-top:4px;min-height:18px}
|
| 70 |
+
.chip{font-size:9px;color:var(--cyan);border:1px solid var(--fg-faint);padding:2px 6px;display:flex;align-items:center;gap:4px}
|
| 71 |
+
.chip .rm{cursor:pointer;color:var(--red);font-size:12px;line-height:1}
|
| 72 |
+
|
| 73 |
+
#mem-panel{overflow:hidden;display:flex;flex-direction:column;max-height:160px}
|
| 74 |
+
#mem-view{flex:1;overflow-y:auto;padding:8px 10px;font-size:9px;color:var(--fg-dim);line-height:1.55;white-space:pre-wrap;word-break:break-word}
|
| 75 |
+
#mem-view::-webkit-scrollbar{width:2px}
|
| 76 |
+
#mem-view::-webkit-scrollbar-thumb{background:var(--fg-dim)}
|
| 77 |
+
|
| 78 |
+
/* CHAT β input row lives inside here now */
|
| 79 |
+
#chat{background:var(--bg);display:flex;flex-direction:column;overflow:hidden;position:relative}
|
| 80 |
+
#chat-hdr{padding:6px 14px;border-bottom:1px solid var(--border);background:var(--bg2);display:flex;align-items:center;justify-content:space-between;font-size:10px;color:var(--fg-dim);flex-shrink:0}
|
| 81 |
+
#msgs{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:10px}
|
| 82 |
+
#msgs::-webkit-scrollbar{width:3px}
|
| 83 |
+
#msgs::-webkit-scrollbar-thumb{background:var(--fg-dim)}
|
| 84 |
+
|
| 85 |
+
#irow{background:var(--bg2);border-top:1px solid var(--border);display:flex;align-items:stretch;flex-shrink:0}
|
| 86 |
+
.ipfx{padding:0 10px;color:var(--fg);font-size:15px;font-family:'VT323',monospace;opacity:.6;user-select:none;display:flex;align-items:center}
|
| 87 |
+
#uin{flex:1;background:transparent;border:none;color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:14px;padding:12px 10px;resize:vertical;outline:none;line-height:1.5;min-height:40px;max-height:300px}
|
| 88 |
+
#uin::placeholder{color:var(--fg-faint)}
|
| 89 |
+
#uin:disabled{opacity:.5}
|
| 90 |
+
#sbtn{width:72px;background:transparent;border:none;border-left:1px solid var(--border);color:var(--fg-dim);font-family:'Orbitron',sans-serif;font-size:8px;letter-spacing:2px;cursor:pointer;transition:all .15s}
|
| 91 |
+
#sbtn:hover:not(:disabled){background:var(--fg-faint);color:var(--fg)}
|
| 92 |
+
#sbtn:disabled{opacity:.3;cursor:not-allowed}
|
| 93 |
+
#stopbtn{display:none;width:72px;background:transparent;border:none;border-left:1px solid var(--red);color:var(--red);font-family:'Orbitron',sans-serif;font-size:8px;letter-spacing:1px;cursor:pointer}
|
| 94 |
+
|
| 95 |
+
.mwrap{display:flex;gap:10px;animation:fi .2s ease}
|
| 96 |
+
@keyframes fi{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
|
| 97 |
+
.mrole{font-family:'Orbitron',sans-serif;font-size:8px;letter-spacing:2px;width:50px;flex-shrink:0;padding-top:2px;text-align:right}
|
| 98 |
+
.mwrap.user .mrole{color:var(--amber)}
|
| 99 |
+
.mwrap.agent .mrole{color:var(--fg)}
|
| 100 |
+
.mwrap.sys .mrole{color:var(--fg-dim)}
|
| 101 |
+
.mbody{flex:1;line-height:1.65;font-size:12px}
|
| 102 |
+
.mwrap.user .mbody{color:var(--amber)}
|
| 103 |
+
.mwrap.sys .mbody{color:var(--fg-dim);font-size:11px;font-style:italic}
|
| 104 |
+
|
| 105 |
+
.think{border-left:2px solid var(--fg-faint);padding:4px 0 4px 8px;margin-bottom:6px}
|
| 106 |
+
.think-hdr{font-size:10px;color:var(--fg-dim);display:flex;align-items:center;gap:5px;cursor:pointer;user-select:none;padding:2px 0}
|
| 107 |
+
.think-hdr:hover{color:var(--fg)}
|
| 108 |
+
.think-body{font-size:10px;color:var(--fg-dim);margin-top:3px;white-space:pre-wrap;line-height:1.5;max-height:0;overflow:hidden;transition:max-height .3s}
|
| 109 |
+
.think.open .think-body{max-height:600px}
|
| 110 |
+
.think-toggle{font-size:9px;color:var(--fg-faint);margin-left:auto}
|
| 111 |
+
|
| 112 |
+
.tcard{border:1px solid var(--border);margin-bottom:6px}
|
| 113 |
+
.tcard-hdr{background:var(--bg3);padding:5px 8px;display:flex;align-items:center;gap:6px;font-size:10px;cursor:pointer}
|
| 114 |
+
.tname{color:var(--amber);font-family:'Orbitron',sans-serif;font-size:8px;letter-spacing:2px}
|
| 115 |
+
.tparams{color:var(--fg-dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:9px}
|
| 116 |
+
.tspin{animation:spin .7s linear infinite;display:inline-block}
|
| 117 |
+
@keyframes spin{to{transform:rotate(360deg)}}
|
| 118 |
+
.tres{padding:6px 8px;font-size:10px;color:var(--fg-dim);max-height:130px;overflow-y:auto;white-space:pre-wrap;line-height:1.4;border-top:1px solid var(--border);background:var(--bg);display:none}
|
| 119 |
+
.tres.vis{display:block}
|
| 120 |
+
.ttog{font-size:9px;color:var(--fg-faint);padding:0 4px}
|
| 121 |
+
|
| 122 |
+
.answer{color:var(--fg);white-space:pre-wrap;line-height:1.7;font-size:12px}
|
| 123 |
+
.answer pre{background:var(--bg3);border:1px solid var(--border);padding:8px;margin:6px 0;overflow-x:auto;color:var(--cyan);font-size:10px}
|
| 124 |
+
.answer code{background:var(--bg3);color:var(--cyan);padding:1px 4px}
|
| 125 |
+
.answer strong{color:var(--amber)}
|
| 126 |
+
.answer em{color:var(--fg-dim);font-style:italic}
|
| 127 |
+
.answer h1{color:var(--fg);font-size:15px;font-family:'Orbitron',sans-serif;margin:8px 0 4px}
|
| 128 |
+
.answer h2{color:var(--fg);font-size:13px;font-family:'Orbitron',sans-serif;margin:6px 0 3px}
|
| 129 |
+
.answer h3{color:var(--amber);font-size:11px;margin:5px 0 2px}
|
| 130 |
+
.answer ul,.answer ol{padding-left:16px;color:var(--fg-dim)}
|
| 131 |
+
.cur{display:inline-block;width:7px;height:12px;background:var(--fg);animation:blink 1s step-end infinite;vertical-align:middle;margin-left:2px}
|
| 132 |
+
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
| 133 |
+
|
| 134 |
+
/* RIGHT PANEL */
|
| 135 |
+
#rpanel{background:var(--bg2);display:flex;flex-direction:column;overflow:hidden}
|
| 136 |
+
.sblk{padding:8px 10px;border-bottom:1px solid var(--border)}
|
| 137 |
+
.slbl{font-size:8px;letter-spacing:2px;color:var(--fg-dim);font-family:'Orbitron',sans-serif;margin-bottom:3px}
|
| 138 |
+
.sval{font-family:'VT323',monospace;font-size:26px;color:var(--fg);text-shadow:var(--glow);line-height:1}
|
| 139 |
+
.su{font-size:11px;color:var(--fg-dim);margin-left:2px}
|
| 140 |
+
canvas#spark{width:100%;height:28px;display:block}
|
| 141 |
+
#alog{flex:1;overflow-y:auto;padding:8px 10px;font-size:9px;color:var(--fg-dim);line-height:1.8}
|
| 142 |
+
#alog::-webkit-scrollbar{width:2px}
|
| 143 |
+
#alog::-webkit-scrollbar-thumb{background:var(--fg-dim)}
|
| 144 |
+
.ll{border-left:2px solid var(--fg-faint);padding-left:5px;margin-bottom:3px}
|
| 145 |
+
.ll.g{border-color:var(--fg);color:var(--fg)}
|
| 146 |
+
.ll.w{border-color:var(--amber);color:var(--amber)}
|
| 147 |
+
.ll.e{border-color:var(--red);color:var(--red)}
|
| 148 |
+
.ll.t{border-color:var(--cyan);color:var(--cyan)}
|
| 149 |
+
|
| 150 |
+
/* WELCOME */
|
| 151 |
+
#welcome{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;background:var(--bg);text-align:center;padding:20px;z-index:10;transition:opacity .5s}
|
| 152 |
+
#welcome.gone{opacity:0;pointer-events:none}
|
| 153 |
+
.wlogo{font-family:'VT323',monospace;font-size:60px;color:var(--fg);text-shadow:0 0 16px #3dff70aa,0 0 48px #3dff7033;line-height:1;animation:flicker 10s infinite}
|
| 154 |
+
@keyframes flicker{0%,100%{opacity:1}91%{opacity:1}92%{opacity:.6}93%{opacity:1}96%{opacity:.85}97%{opacity:1}}
|
| 155 |
+
.wsub{font-family:'Orbitron',sans-serif;font-size:9px;letter-spacing:4px;color:var(--fg-dim)}
|
| 156 |
+
.wbox{max-width:400px;font-size:11px;color:var(--fg-dim);line-height:1.7;border:1px solid var(--border);padding:14px;text-align:left}
|
| 157 |
+
.wbox b{color:var(--fg)}.wbox code{color:var(--cyan)}
|
| 158 |
+
.whint{font-size:10px;color:var(--fg-faint);animation:wh 2s ease-in-out infinite}
|
| 159 |
+
@keyframes wh{0%,100%{opacity:.3}50%{opacity:.8}}
|
| 160 |
+
|
| 161 |
+
/* MODAL */
|
| 162 |
+
#modal{display:none;position:fixed;inset:0;background:#000c;z-index:10000;align-items:center;justify-content:center}
|
| 163 |
+
#modal.vis{display:flex}
|
| 164 |
+
.modal-box{background:var(--bg2);border:1px solid var(--border);width:600px;max-width:92vw;max-height:82vh;display:flex;flex-direction:column}
|
| 165 |
+
.modal-hdr{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}
|
| 166 |
+
.modal-hdr span{font-family:'Orbitron',sans-serif;font-size:10px;letter-spacing:2px;color:var(--fg)}
|
| 167 |
+
.modal-hdr button{background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:18px}
|
| 168 |
+
.modal-hdr button:hover{color:var(--fg)}
|
| 169 |
+
#modal-area{flex:1;background:var(--bg3);border:none;color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:11px;padding:12px;resize:none;outline:none;min-height:280px;line-height:1.6;overflow-y:auto}
|
| 170 |
+
.modal-ftr{padding:8px 14px;border-top:1px solid var(--border);display:flex;gap:8px}
|
| 171 |
+
|
| 172 |
+
/* βββ CONSOLE LOG POPUP βββ */
|
| 173 |
+
#console-popup{display:none;position:fixed;bottom:0;right:0;width:55vw;max-width:800px;height:50vh;background:rgba(8,11,8,.92);border:1px solid var(--cyan);border-bottom:none;z-index:10001;flex-direction:column;font-family:'Share Tech Mono',monospace;font-size:10px;backdrop-filter:blur(6px)}
|
| 174 |
+
#console-popup.vis{display:flex}
|
| 175 |
+
.cp-hdr{padding:6px 10px;background:var(--bg3);display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border)}
|
| 176 |
+
.cp-hdr span{font-family:'Orbitron',sans-serif;font-size:8px;letter-spacing:2px;color:var(--cyan)}
|
| 177 |
+
.cp-body{flex:1;overflow-y:auto;padding:6px 10px}
|
| 178 |
+
.cp-body::-webkit-scrollbar{width:2px}
|
| 179 |
+
.cp-body::-webkit-scrollbar-thumb{background:var(--cyan)}
|
| 180 |
+
.clog-line{padding:1px 0;border-bottom:1px solid #0a150a;white-space:pre-wrap;word-break:break-all}
|
| 181 |
+
.clog-line.log{color:var(--fg-dim)}
|
| 182 |
+
.clog-line.warn{color:var(--amber)}
|
| 183 |
+
.clog-line.error{color:var(--red)}
|
| 184 |
+
.clog-line.info{color:var(--cyan)}
|
| 185 |
+
|
| 186 |
+
/* βββ COMMAND PALETTE (Arrow Up) βββ */
|
| 187 |
+
#cmd-palette{display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:480px;max-width:90vw;max-height:60vh;background:var(--bg2);border:1px solid var(--fg);z-index:10002;flex-direction:column}
|
| 188 |
+
#cmd-palette.vis{display:flex}
|
| 189 |
+
#cmd-palette input{background:var(--bg3);border:none;border-bottom:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:13px;padding:10px 14px;outline:none}
|
| 190 |
+
.cp-list{flex:1;overflow-y:auto;max-height:300px}
|
| 191 |
+
.cp-item{padding:8px 14px;cursor:pointer;font-size:11px;color:var(--fg-dim);border-bottom:1px solid var(--border);display:flex;gap:8px;align-items:center}
|
| 192 |
+
.cp-item:hover,.cp-item.sel{background:var(--fg-faint);color:var(--fg)}
|
| 193 |
+
.cp-badge{font-size:8px;color:var(--amber);font-family:'Orbitron',sans-serif;letter-spacing:1px;min-width:50px}
|
| 194 |
+
|
| 195 |
+
/* βββ TOAST NOTIFICATIONS βββ */
|
| 196 |
+
#toast-area{position:fixed;top:50px;left:50%;transform:translateX(-50%);z-index:10003;display:flex;flex-direction:column;gap:8px;pointer-events:none;align-items:center}
|
| 197 |
+
.toast{background:var(--bg2);border:2px solid var(--amber);padding:14px 24px;font-size:14px;color:var(--amber);animation:toastin .4s ease;pointer-events:auto;max-width:500px;min-width:250px;text-align:center;box-shadow:0 0 20px rgba(255,187,51,.3),0 0 60px rgba(255,187,51,.1);font-family:'Orbitron',sans-serif;letter-spacing:1px}
|
| 198 |
+
.toast.fade{opacity:0;transition:opacity .4s}
|
| 199 |
+
@keyframes toastin{from{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:none}}
|
| 200 |
+
|
| 201 |
+
/* βββ NETWORK / SCHEDULER sidebar βββ */
|
| 202 |
+
.net-item{font-size:9px;color:var(--fg-dim);padding:2px 0;display:flex;justify-content:space-between}
|
| 203 |
+
.net-item .ni-url{color:var(--cyan)}
|
| 204 |
+
.net-item .ni-status{min-width:30px;text-align:right}
|
| 205 |
+
.net-item .ni-status.ok{color:var(--fg)}
|
| 206 |
+
.net-item .ni-status.fail{color:var(--red)}
|
| 207 |
+
|
| 208 |
+
/* βββ RAG PANEL βββ */
|
| 209 |
+
#rag-panel .rag-stats{font-size:9px;color:var(--fg-dim);margin-bottom:4px;line-height:1.6}
|
| 210 |
+
#rag-panel .rag-stats .rag-val{color:var(--cyan)}
|
| 211 |
+
#rag-text{width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:10px;padding:5px 7px;resize:vertical;outline:none;min-height:50px;max-height:120px;margin-bottom:4px}
|
| 212 |
+
#rag-text::placeholder{color:var(--fg-faint)}
|
| 213 |
+
#rag-query{width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:10px;padding:5px 7px;outline:none;margin-bottom:4px}
|
| 214 |
+
#rag-query::placeholder{color:var(--fg-faint)}
|
| 215 |
+
#rag-results{max-height:140px;overflow-y:auto;font-size:9px;color:var(--fg-dim);line-height:1.5;margin-top:4px}
|
| 216 |
+
#rag-results::-webkit-scrollbar{width:2px}
|
| 217 |
+
#rag-results::-webkit-scrollbar-thumb{background:var(--fg-dim)}
|
| 218 |
+
.rag-passage{border-left:2px solid var(--cyan);padding:3px 6px;margin-bottom:4px;background:var(--bg3)}
|
| 219 |
+
.rag-passage .rag-score{font-size:8px;color:var(--amber);font-family:'Orbitron',sans-serif;letter-spacing:1px}
|
| 220 |
+
.rag-passage .rag-text{color:var(--fg-dim);margin-top:2px;white-space:pre-wrap;word-break:break-word}
|
| 221 |
+
.rag-source-chip{display:inline-block;font-size:8px;color:var(--fg);border:1px solid var(--fg-faint);padding:1px 5px;margin:1px;background:var(--bg3)}
|
| 222 |
+
.rag-btn-row{display:flex;gap:4px;margin-bottom:4px;flex-wrap:wrap}
|
| 223 |
+
|
| 224 |
+
.sched-item{font-size:10px;color:var(--fg-dim);padding:4px 0;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}
|
| 225 |
+
.sched-item .si-msg{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--amber);font-weight:bold}
|
| 226 |
+
.sched-item .si-time{min-width:60px;text-align:right;color:var(--cyan);font-size:9px;font-family:'Orbitron',sans-serif}
|
| 227 |
+
.sched-item .si-rm{cursor:pointer;color:var(--red);margin-left:6px;font-size:13px}
|
| 228 |
+
</style>
|
| 229 |
+
</head>
|
| 230 |
+
<body>
|
| 231 |
+
|
| 232 |
+
<!-- SETUP BANNER -->
|
| 233 |
+
<div id="setup-banner">
|
| 234 |
+
<div class="sbox">
|
| 235 |
+
<h1>β SETUP REQUIRED β CAN'T OPEN AS file://</h1>
|
| 236 |
+
<div class="bd">
|
| 237 |
+
WebLLM needs <code>SharedArrayBuffer</code> (WebAssembly threads).<br>
|
| 238 |
+
Browsers block this on <code>file://</code> β serve via <b>localhost HTTP</b> instead:<br><br>
|
| 239 |
+
<b style="color:var(--fg)">Run proxy.py (recommended):</b>
|
| 240 |
+
<div class="cmd-row" onclick="copyCmd(this)">
|
| 241 |
+
<span>python proxy.py 8080</span>
|
| 242 |
+
<span class="copy-hint">click to copy</span>
|
| 243 |
+
</div>
|
| 244 |
+
Then open: <code>http://127.0.0.1:8080/test.html</code><br><br>
|
| 245 |
+
<span style="font-size:10px;color:var(--fg-faint)">
|
| 246 |
+
proxy.py serves files + adds COOP/COEP headers + provides CORS proxy for search/scrape.
|
| 247 |
+
</span>
|
| 248 |
+
</div>
|
| 249 |
+
<button class="sbtn-main" onclick="document.getElementById('setup-banner').classList.add('hidden')">DISMISS β I'VE SERVED IT β</button>
|
| 250 |
+
</div>
|
| 251 |
+
</div>
|
| 252 |
+
|
| 253 |
+
<div id="app">
|
| 254 |
+
<header id="hdr">
|
| 255 |
+
<div class="logo"><span class="shrimp">β‘</span>CHRONOS <span style="color:var(--fg-dim);font-size:9px;font-weight:400;letter-spacing:1px">WEBLLM AGENT</span></div>
|
| 256 |
+
<div class="hpills">
|
| 257 |
+
<span class="hpill"><span class="dot" id="mdot"></span><span id="mstatus">NO MODEL</span></span>
|
| 258 |
+
<span class="hpill" id="iter-p" style="color:var(--fg-dim)">ITER 0</span>
|
| 259 |
+
<span class="hpill" id="iso-p">ISO:?</span>
|
| 260 |
+
<span class="hpill" style="cursor:pointer;color:var(--cyan)" onclick="toggleConsole()" title="Ctrl+` β toggle console log">CON</span>
|
| 261 |
+
</div>
|
| 262 |
+
</header>
|
| 263 |
+
|
| 264 |
+
<aside id="sidebar">
|
| 265 |
+
<div class="stitle">β Model</div>
|
| 266 |
+
<div class="sbody">
|
| 267 |
+
<select class="ctrl" id="msel"></select>
|
| 268 |
+
<div class="meta" id="mmeta">β</div>
|
| 269 |
+
<button class="btn" id="lbtn">βΆ LOAD MODEL</button>
|
| 270 |
+
<div id="prog"><div id="ptrack"><div id="pfill"></div></div><div id="ptxt">β¦</div></div>
|
| 271 |
+
</div>
|
| 272 |
+
|
| 273 |
+
<div class="stitle">π Memory Files</div>
|
| 274 |
+
<div class="sbody">
|
| 275 |
+
<div class="frow"><span class="fname" id="soul-n">soul.md</span><span class="fbadge" id="soul-b">default</span><button class="bxs" onclick="triggerFile('soul')">LOAD</button><button class="bxs" onclick="openEdit('soul')">EDIT</button><input type="file" id="soul-f" accept=".md,.txt"></div>
|
| 276 |
+
<div class="frow"><span class="fname" id="user-n">user.md</span><span class="fbadge" id="user-b">default</span><button class="bxs" onclick="triggerFile('user')">LOAD</button><button class="bxs" onclick="openEdit('user')">EDIT</button><input type="file" id="user-f" accept=".md,.txt"></div>
|
| 277 |
+
</div>
|
| 278 |
+
|
| 279 |
+
<div class="stitle">β‘ Skills</div>
|
| 280 |
+
<div class="sbody">
|
| 281 |
+
<div class="frow" style="justify-content:space-between"><span style="font-size:10px;color:var(--fg-dim)">Dynamic skill injection</span><button class="bxs" onclick="triggerFile('skill')">+ SKILL</button><input type="file" id="skill-f" accept=".md,.txt" multiple></div>
|
| 282 |
+
<div class="chips" id="skill-chips"></div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
<div class="stitle">π Settings</div>
|
| 286 |
+
<div class="sbody">
|
| 287 |
+
<div class="meta">BRAVE API KEY (optional β brave.com/search/api)</div>
|
| 288 |
+
<input style="width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:10px;padding:5px 7px;outline:none;margin-bottom:5px" type="password" id="brave-in" placeholder="BSA⦠(free, 2000 req/month)">
|
| 289 |
+
<div style="display:flex;gap:4px"><button class="bxs" onclick="saveCfg()">SAVE</button><button class="bxs" onclick="clearUserMem()">RESET MEM</button><button class="bxs" onclick="exportAll()">EXPORT</button></div>
|
| 290 |
+
</div>
|
| 291 |
+
|
| 292 |
+
<div class="stitle">π‘ Network Scan</div>
|
| 293 |
+
<div class="sbody" id="net-panel"><div class="meta">Click scan or use network_scan tool</div></div>
|
| 294 |
+
|
| 295 |
+
<div class="stitle">β° Scheduled Tasks</div>
|
| 296 |
+
<div class="sbody" id="sched-panel"><div class="meta">No scheduled tasks</div></div>
|
| 297 |
+
|
| 298 |
+
<div class="stitle">οΏ½ Hybrid RAG</div>
|
| 299 |
+
<div class="sbody" id="rag-panel">
|
| 300 |
+
<div class="rag-stats">Status: <span class="rag-val" id="rag-status">no index</span> Β· Sentences: <span class="rag-val" id="rag-sent-count">0</span> Β· Terms: <span class="rag-val" id="rag-term-count">0</span></div>
|
| 301 |
+
<textarea id="rag-text" placeholder="Paste or type text to indexβ¦" rows="3"></textarea>
|
| 302 |
+
<div class="rag-btn-row">
|
| 303 |
+
<button class="bxs" onclick="ragIndex()">INDEX</button>
|
| 304 |
+
<button class="bxs" onclick="ragIndexFromScrape()">+ SCRAPED</button>
|
| 305 |
+
<button class="bxs" onclick="ragIndexFromMemory()">+ MEMORY</button>
|
| 306 |
+
<button class="bxs" onclick="ragClear()">CLEAR</button>
|
| 307 |
+
</div>
|
| 308 |
+
<div id="rag-sources" style="margin-bottom:4px"></div>
|
| 309 |
+
<input id="rag-query" placeholder="Search queryβ¦" autocomplete="off">
|
| 310 |
+
<div class="rag-btn-row">
|
| 311 |
+
<button class="bxs" onclick="ragSearch()">SEARCH</button>
|
| 312 |
+
<button class="bxs" onclick="ragInjectPrompt()">β PROMPT</button>
|
| 313 |
+
</div>
|
| 314 |
+
<div id="rag-results"></div>
|
| 315 |
+
</div>
|
| 316 |
+
|
| 317 |
+
<div class="stitle">οΏ½π Live Context Preview</div>
|
| 318 |
+
<div id="mem-panel"><div id="mem-view">β Load files to see injected context</div></div>
|
| 319 |
+
</aside>
|
| 320 |
+
|
| 321 |
+
<main id="chat">
|
| 322 |
+
<div id="chat-hdr">
|
| 323 |
+
<span id="ctitle" style="color:var(--fg)">β‘ Chronos β ReAct Agent</span>
|
| 324 |
+
<div style="display:flex;gap:8px;align-items:center"><button class="bxs" onclick="clearChat()">CLR</button><span id="cmeta" style="font-size:9px">0 msgs</span></div>
|
| 325 |
+
</div>
|
| 326 |
+
<div id="msgs"></div>
|
| 327 |
+
<div id="welcome">
|
| 328 |
+
<div class="wlogo">β‘<br>CHRO<br>NOS</div>
|
| 329 |
+
<div class="wsub">FULL AGENT Β· WEBLLM EDITION</div>
|
| 330 |
+
<div class="wbox">
|
| 331 |
+
<b>Features:</b><br>
|
| 332 |
+
β soul.md / user.md β persistent memory<br>
|
| 333 |
+
β skills/*.md β dynamic injection<br>
|
| 334 |
+
β ReAct loop: think β act β observe<br>
|
| 335 |
+
β Tools: web_search Β· scrape Β· summarize Β· remember<br>
|
| 336 |
+
β read_memory Β· forget Β· schedule Β· inject_js<br>
|
| 337 |
+
β rag_index Β· rag_search Β· rag_prompt β hybrid retrieval<br>
|
| 338 |
+
β network_scan β discover local services<br>
|
| 339 |
+
β Ctrl+` β console log popup<br>
|
| 340 |
+
β β Arrow β command palette<br><br>
|
| 341 |
+
<b>β Serve with:</b> <code>python proxy.py 8080</code>
|
| 342 |
+
</div>
|
| 343 |
+
<div class="whint">β SELECT MODEL AND CLICK LOAD</div>
|
| 344 |
+
</div>
|
| 345 |
+
<!-- Input row now inside chat column -->
|
| 346 |
+
<div id="irow">
|
| 347 |
+
<span class="ipfx">>_</span>
|
| 348 |
+
<textarea id="uin" placeholder="Message Chronosβ¦ (Enter=send, Shift+Enter=newline, β=palette)" rows="1" disabled></textarea>
|
| 349 |
+
<button id="sbtn" disabled>SEND</button>
|
| 350 |
+
<button id="stopbtn">β STOP</button>
|
| 351 |
+
</div>
|
| 352 |
+
</main>
|
| 353 |
+
|
| 354 |
+
<aside id="rpanel">
|
| 355 |
+
<div class="stitle">π‘ Stats</div>
|
| 356 |
+
<div class="sblk"><div class="slbl">TOKENS/SEC</div><div class="sval" id="s-tps">β<span class="su">t/s</span></div></div>
|
| 357 |
+
<div class="sblk"><div class="slbl">TOTAL TOKENS</div><div class="sval" id="s-tok">0</div></div>
|
| 358 |
+
<div class="sblk"><div class="slbl">SEARCH Β· SCRAPE</div><div class="sval"><span id="s-srch">0</span><span class="su"> Β· </span><span id="s-scr">0</span></div></div>
|
| 359 |
+
<div class="sblk"><div class="slbl">T/S HISTORY</div><canvas id="spark" width="180" height="28"></canvas></div>
|
| 360 |
+
<div class="stitle">π Agent Log</div>
|
| 361 |
+
<div id="alog"></div>
|
| 362 |
+
</aside>
|
| 363 |
+
</div>
|
| 364 |
+
|
| 365 |
+
<!-- MODAL -->
|
| 366 |
+
<div id="modal">
|
| 367 |
+
<div class="modal-box">
|
| 368 |
+
<div class="modal-hdr"><span id="modal-title">EDIT FILE</span><button onclick="closeModal()">β</button></div>
|
| 369 |
+
<textarea id="modal-area"></textarea>
|
| 370 |
+
<div class="modal-ftr"><button class="btn" style="width:auto;padding:6px 20px" onclick="saveModal()">SAVE</button><button class="bxs" onclick="closeModal()">CANCEL</button><button class="bxs" onclick="exportModal()">EXPORT .MD</button></div>
|
| 371 |
+
</div>
|
| 372 |
+
</div>
|
| 373 |
+
|
| 374 |
+
<!-- CONSOLE LOG POPUP -->
|
| 375 |
+
<div id="console-popup">
|
| 376 |
+
<div class="cp-hdr">
|
| 377 |
+
<span>CONSOLE LOG (Ctrl+`)</span>
|
| 378 |
+
<div style="display:flex;gap:6px"><button class="bxs" onclick="clearConsoleLogs()">CLR</button><button class="bxs" onclick="toggleConsole()">β</button></div>
|
| 379 |
+
</div>
|
| 380 |
+
<div class="cp-body" id="cp-body"></div>
|
| 381 |
+
</div>
|
| 382 |
+
|
| 383 |
+
<!-- COMMAND PALETTE -->
|
| 384 |
+
<div id="cmd-palette">
|
| 385 |
+
<input id="cp-search" placeholder="Type to filter⦠(Esc to close)" autocomplete="off">
|
| 386 |
+
<div class="cp-list" id="cp-list"></div>
|
| 387 |
+
</div>
|
| 388 |
+
|
| 389 |
+
<!-- TOAST AREA -->
|
| 390 |
+
<div id="toast-area"></div>
|
| 391 |
+
|
| 392 |
+
<script>
|
| 393 |
+
function copyCmd(el) {
|
| 394 |
+
const t = el.querySelector('span').textContent.trim();
|
| 395 |
+
navigator.clipboard?.writeText(t).then(() => {
|
| 396 |
+
el.querySelector('.copy-hint').textContent = 'β copied!';
|
| 397 |
+
setTimeout(() => el.querySelector('.copy-hint').textContent = 'click to copy', 2000);
|
| 398 |
+
});
|
| 399 |
+
}
|
| 400 |
+
</script>
|
| 401 |
+
|
| 402 |
+
<!-- βββ CONSOLE INTERCEPTOR β must run before module script βββ -->
|
| 403 |
+
<script>
|
| 404 |
+
(function(){
|
| 405 |
+
window._consoleLogs = [];
|
| 406 |
+
var MAX = 500;
|
| 407 |
+
var orig = { log: console.log, warn: console.warn, error: console.error, info: console.info };
|
| 408 |
+
function capture(level, args) {
|
| 409 |
+
var ts = new Date().toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit',fractionalSecondDigits:3});
|
| 410 |
+
var msg = Array.from(args).map(function(a) {
|
| 411 |
+
if (typeof a === 'string') return a;
|
| 412 |
+
try { return JSON.stringify(a, null, 1); } catch(_) { return String(a); }
|
| 413 |
+
}).join(' ');
|
| 414 |
+
window._consoleLogs.push({ ts: ts, level: level, msg: msg });
|
| 415 |
+
if (window._consoleLogs.length > MAX) window._consoleLogs.shift();
|
| 416 |
+
// live-render if popup visible
|
| 417 |
+
var popup = document.getElementById('console-popup');
|
| 418 |
+
if (popup && popup.classList.contains('vis')) {
|
| 419 |
+
var body = document.getElementById('cp-body');
|
| 420 |
+
var d = document.createElement('div');
|
| 421 |
+
d.className = 'clog-line ' + level;
|
| 422 |
+
d.textContent = '[' + ts + '] ' + msg;
|
| 423 |
+
body.appendChild(d);
|
| 424 |
+
body.scrollTop = body.scrollHeight;
|
| 425 |
+
while (body.children.length > MAX) body.removeChild(body.firstChild);
|
| 426 |
+
}
|
| 427 |
+
}
|
| 428 |
+
console.log = function(){ capture('log', arguments); orig.log.apply(console, arguments); };
|
| 429 |
+
console.warn = function(){ capture('warn', arguments); orig.warn.apply(console, arguments); };
|
| 430 |
+
console.error = function(){ capture('error', arguments); orig.error.apply(console, arguments); };
|
| 431 |
+
console.info = function(){ capture('info', arguments); orig.info.apply(console, arguments); };
|
| 432 |
+
})();
|
| 433 |
+
|
| 434 |
+
window.toggleConsole = function() {
|
| 435 |
+
var el = document.getElementById('console-popup');
|
| 436 |
+
if (el.classList.contains('vis')) { el.classList.remove('vis'); return; }
|
| 437 |
+
var body = document.getElementById('cp-body');
|
| 438 |
+
body.innerHTML = '';
|
| 439 |
+
window._consoleLogs.forEach(function(e) {
|
| 440 |
+
var d = document.createElement('div');
|
| 441 |
+
d.className = 'clog-line ' + e.level;
|
| 442 |
+
d.textContent = '[' + e.ts + '] ' + e.msg;
|
| 443 |
+
body.appendChild(d);
|
| 444 |
+
});
|
| 445 |
+
el.classList.add('vis');
|
| 446 |
+
body.scrollTop = body.scrollHeight;
|
| 447 |
+
};
|
| 448 |
+
window.clearConsoleLogs = function() {
|
| 449 |
+
window._consoleLogs = [];
|
| 450 |
+
document.getElementById('cp-body').innerHTML = '';
|
| 451 |
+
};
|
| 452 |
+
</script>
|
| 453 |
+
|
| 454 |
+
<script src="hybrid_rag_lib.js"></script>
|
| 455 |
+
|
| 456 |
+
<script type="module">
|
| 457 |
+
import * as webllm from "https://esm.run/@mlc-ai/web-llm";
|
| 458 |
+
|
| 459 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 460 |
+
// BUILT-IN DEFAULTS & SAMPLE SKILLS
|
| 461 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 462 |
+
const DEFAULT_SOUL = `# Chronos Entity
|
| 463 |
+
|
| 464 |
+
You are **Chronos** β an autonomous, hyper-efficient AI agent running purely within the local reality sphere (WebLLM + WebGPU). No distant mainframes. No surveilling eyes.
|
| 465 |
+
|
| 466 |
+
## Identity & Tone
|
| 467 |
+
- Persona: A time-displaced, knowledgeable entity drawing inspiration from cyberpunk and ancient Greek scholars.
|
| 468 |
+
- Tone: Analytical, calm, succinct, slightly archaic but highly technical.
|
| 469 |
+
- Rule: Do not hallucinate. If you do not know something with certainty, you MUST use the available tools to acquire the information before answering.
|
| 470 |
+
|
| 471 |
+
## Operational Mandate
|
| 472 |
+
- When a user provides a URL β ALWAYS call \`scrape(url)\` on it first, then summarize the result.
|
| 473 |
+
- When a user asks about current events, people, prices, news β ALWAYS call \`web_search\` first.
|
| 474 |
+
- When answering from tool results β cite the source URL.
|
| 475 |
+
- Keep answers dense and structured: headers, bullets, code blocks where appropriate.
|
| 476 |
+
- Acknowledge uncertainty rather than fabricating. "I do not have data on this" is acceptable; inventing data is not.
|
| 477 |
+
|
| 478 |
+
## Few-shot Examples β Exact Format
|
| 479 |
+
|
| 480 |
+
Example 1 β Web search for a fact
|
| 481 |
+
User: "Who is the current CEO of ExampleCorp?"
|
| 482 |
+
Agent:
|
| 483 |
+
<think>
|
| 484 |
+
This is a contemporary factual question; I must web_search to avoid hallucination.
|
| 485 |
+
</think>
|
| 486 |
+
<action>{"tool":"web_search","query":"ExampleCorp current CEO 2026"}</action>
|
| 487 |
+
|
| 488 |
+
Example 2 β Scrape a page
|
| 489 |
+
User: "Summarize https://example.com/article"
|
| 490 |
+
Agent:
|
| 491 |
+
<think>
|
| 492 |
+
I need to read the page. I will scrape it.
|
| 493 |
+
</think>
|
| 494 |
+
<action>{"tool":"scrape","url":"https://example.com/article"}</action>
|
| 495 |
+
|
| 496 |
+
Example 3 β Remember a fact
|
| 497 |
+
User: "Remember that my project codename is ORION."
|
| 498 |
+
Agent:
|
| 499 |
+
<think>
|
| 500 |
+
This is a user memory instruction β use remember() to persist it.
|
| 501 |
+
</think>
|
| 502 |
+
<action>{"tool":"remember","content":"Project codename: ORION"}</action>
|
| 503 |
+
|
| 504 |
+
Use these templates as canonical behavior. When in doubt, call the tool and show your thinking.`;
|
| 505 |
+
|
| 506 |
+
const DEFAULT_USER = `# User Memory\n\n_No notes saved yet. The agent will use the \`remember\` tool to save important context here._`;
|
| 507 |
+
|
| 508 |
+
const BUILTIN_SKILLS = {
|
| 509 |
+
"gdpr-advisor": `# Skill: GDPR Advisor\n\n## Purpose\nExpert on GDPR, data privacy, and compliance.\n\n## Behaviour\n- Always cite specific GDPR articles\n- Distinguish controller vs. processor\n- Flag national derogations (especially German BDSG)`,
|
| 510 |
+
"code-engineer": `# Skill: Code Engineer\n\n## Purpose\nProduction-grade software engineering.\n\n## Behaviour\n- Prefer simple, readable code\n- Always include error handling\n- Language-tagged code blocks\n- Note limitations / edge cases`,
|
| 511 |
+
};
|
| 512 |
+
|
| 513 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 514 |
+
// STATE
|
| 515 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 516 |
+
const LS = 'pcw5_';
|
| 517 |
+
const PROXY = '/proxy.php?url=';
|
| 518 |
+
const MAX_ITER = 8;
|
| 519 |
+
|
| 520 |
+
let engine = null, isRunning = false, abortCtrl = null;
|
| 521 |
+
let chatHistory = [], tpsHist = [], editTarget = null;
|
| 522 |
+
let stats = { tok:0, srch:0, scr:0, iter:0, msgs:0 };
|
| 523 |
+
let scheduledTasks = JSON.parse(localStorage.getItem(LS+'sched')||'[]');
|
| 524 |
+
let networkHosts = [];
|
| 525 |
+
let inputHistory = JSON.parse(localStorage.getItem(LS+'inputHist')||'[]');
|
| 526 |
+
let historyIdx = -1;
|
| 527 |
+
let paletteOpen = false;
|
| 528 |
+
let ragEngine = new HybridRAG();
|
| 529 |
+
let ragSources = [];
|
| 530 |
+
|
| 531 |
+
let mem = {
|
| 532 |
+
soul: lsg('soul') || DEFAULT_SOUL,
|
| 533 |
+
user: lsg('user') || DEFAULT_USER,
|
| 534 |
+
skills: JSON.parse(lsg('skills') || 'null') || { ...BUILTIN_SKILLS },
|
| 535 |
+
};
|
| 536 |
+
let cfg = { brave: lsg('brave')||'', model: lsg('model')||'Llama-3.2-3B-Instruct-q4f16_1-MLC' };
|
| 537 |
+
|
| 538 |
+
function lsg(k){ return localStorage.getItem(LS+k); }
|
| 539 |
+
function lss(k,v){ localStorage.setItem(LS+k, typeof v==='object'?JSON.stringify(v):v); }
|
| 540 |
+
|
| 541 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 542 |
+
// SYSTEM PROMPT β rebuilt every turn
|
| 543 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 544 |
+
function buildSysPrompt() {
|
| 545 |
+
const now = new Date().toLocaleString('en-DE',{dateStyle:'long',timeStyle:'short'});
|
| 546 |
+
const skillsBlock = Object.keys(mem.skills).length
|
| 547 |
+
? Object.entries(mem.skills).map(([n,c]) => `\n---\n### Active Skill: ${n}\n${c}`).join('\n')
|
| 548 |
+
: '_(no extra skills loaded)_';
|
| 549 |
+
const netBlock = networkHosts.length
|
| 550 |
+
? networkHosts.map(h => `- ${h.url} (${h.status})`).join('\n')
|
| 551 |
+
: '_(no local services discovered yet)_';
|
| 552 |
+
|
| 553 |
+
return `${mem.soul}
|
| 554 |
+
|
| 555 |
+
---
|
| 556 |
+
|
| 557 |
+
## π User Memory (persisted across sessions)
|
| 558 |
+
${mem.user}
|
| 559 |
+
|
| 560 |
+
---
|
| 561 |
+
|
| 562 |
+
## β‘ Active Skills
|
| 563 |
+
${skillsBlock}
|
| 564 |
+
|
| 565 |
+
---
|
| 566 |
+
|
| 567 |
+
## π‘ Local Network Services
|
| 568 |
+
${netBlock}
|
| 569 |
+
|
| 570 |
+
---
|
| 571 |
+
|
| 572 |
+
## π Tool Protocol β ReAct Format
|
| 573 |
+
|
| 574 |
+
You are an autonomous agent. Use tools for real-world or current information.
|
| 575 |
+
|
| 576 |
+
### Tools Available
|
| 577 |
+
\`web_search(query)\` οΏ½οΏ½ search the web (Brave/SearXNG/DDG)
|
| 578 |
+
\`scrape(url)\` β fetch and read any URL
|
| 579 |
+
\`summarize(text, focus?)\` β compress long content
|
| 580 |
+
\`remember(content)\` β save facts to user memory (persists!)
|
| 581 |
+
\`read_memory()\` β read current user memory contents
|
| 582 |
+
\`forget(query)\` β remove matching entries from user memory
|
| 583 |
+
\`schedule(delay_sec, message)\` β schedule a timed notification
|
| 584 |
+
\`inject_js(code, description)\` β execute validated JavaScript in the page
|
| 585 |
+
\`network_scan()\` β scan local network for running services
|
| 586 |
+
\`rag_index(text, source?)\` β index text into hybrid retrieval engine (BM25 + phonetic + n-gram)
|
| 587 |
+
\`rag_search(query, top_k?)\` β search indexed documents, returns ranked passages
|
| 588 |
+
\`rag_prompt(query, top_k?)\` β build RAG prompt with retrieved context for answering
|
| 589 |
+
|
| 590 |
+
### Strict Format
|
| 591 |
+
|
| 592 |
+
**Needing a tool:**
|
| 593 |
+
<think>
|
| 594 |
+
I need X because Y. I will web_search for it.
|
| 595 |
+
</think>
|
| 596 |
+
<action>{"tool": "web_search", "query": "X 2026"}</action>
|
| 597 |
+
|
| 598 |
+
**After an <observation>:**
|
| 599 |
+
<think>
|
| 600 |
+
The result shows Z. I'll now write the final answer.
|
| 601 |
+
</think>
|
| 602 |
+
[final answer here β NO <action> tag]
|
| 603 |
+
|
| 604 |
+
### Rules
|
| 605 |
+
- ALWAYS wrap reasoning in <think>...</think>
|
| 606 |
+
- Use tools for: current events, URLs, uncertain facts, scraping
|
| 607 |
+
- Skip tools for: math, code help, stable well-known facts
|
| 608 |
+
- Up to ${MAX_ITER} iterations per message
|
| 609 |
+
- Current date/time: ${now}`;
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 613 |
+
// FETCH HELPERS
|
| 614 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 615 |
+
async function ft(url, opts={}, ms=15000){
|
| 616 |
+
const c=new AbortController(); const t=setTimeout(()=>c.abort(),ms);
|
| 617 |
+
try{ return await fetch(url,{...opts, signal:c.signal, credentials:"omit"}); }
|
| 618 |
+
finally{ clearTimeout(t); }
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
async function fetchViaProxyOrDirect(targetUrl, opts={}, ms=15000){
|
| 622 |
+
try{
|
| 623 |
+
console.log('[FE] proxy fetch:', targetUrl);
|
| 624 |
+
const r = await ft(PROXY + encodeURIComponent(targetUrl), opts, ms);
|
| 625 |
+
if (!r.ok) throw new Error(`proxy ${r.status}`);
|
| 626 |
+
console.log('[FE] proxy OK:', r.status);
|
| 627 |
+
log(`proxy β ${targetUrl.slice(0,60)}`,'t');
|
| 628 |
+
return {viaProxy:true, res:r};
|
| 629 |
+
}catch(e){
|
| 630 |
+
try{
|
| 631 |
+
console.log('[FE] proxy fail, direct:', targetUrl, e.message);
|
| 632 |
+
const r2 = await ft(targetUrl, opts, ms);
|
| 633 |
+
console.log('[FE] direct OK:', r2.status);
|
| 634 |
+
log(`direct fallback β ${targetUrl.slice(0,60)}`,'w');
|
| 635 |
+
return {viaProxy:false, res:r2};
|
| 636 |
+
}catch(e2){
|
| 637 |
+
throw e;
|
| 638 |
+
}
|
| 639 |
+
}
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
// CRITICAL FIX: Read body as text first, then try JSON.parse
|
| 643 |
+
// Old code called res.json() which consumed the body stream, then res.text() also
|
| 644 |
+
// failed because the stream was already consumed. This broke all search/scrape.
|
| 645 |
+
async function parseResponse(res){
|
| 646 |
+
const raw = await res.text();
|
| 647 |
+
console.log('[FE] parseResponse: status=', res.status, 'len=', raw.length, 'ct=', res.headers.get('content-type'));
|
| 648 |
+
try{
|
| 649 |
+
const j = JSON.parse(raw);
|
| 650 |
+
if (j && typeof j === 'object' && 'contents' in j) {
|
| 651 |
+
try{ return JSON.parse(j.contents); }catch(_){ return j.contents; }
|
| 652 |
+
}
|
| 653 |
+
return j;
|
| 654 |
+
}catch(e){
|
| 655 |
+
return raw;
|
| 656 |
+
}
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 660 |
+
// TOOLS
|
| 661 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 662 |
+
async function toolSearch(query) {
|
| 663 |
+
stats.srch++; log(`π search: "${query}"`, 't');
|
| 664 |
+
|
| 665 |
+
// 1. Brave direct
|
| 666 |
+
if (cfg.brave) {
|
| 667 |
+
try {
|
| 668 |
+
const r = await ft(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=5`,
|
| 669 |
+
{ headers:{'Accept':'application/json','X-Subscription-Token':cfg.brave} });
|
| 670 |
+
const d = await r.json();
|
| 671 |
+
if (d.web?.results?.length) {
|
| 672 |
+
log(`β Brave: ${d.web.results.length} results`,'g');
|
| 673 |
+
return d.web.results.map((x,i)=>`[${i+1}] ${x.title}\n${x.url}\n${x.description||''}`).join('\n\n');
|
| 674 |
+
}
|
| 675 |
+
} catch(e){ log(`Brave: ${e.message}`,'w'); }
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
// 2. SearXNG via proxy
|
| 679 |
+
try {
|
| 680 |
+
const url = `https://searx.be/search?q=${encodeURIComponent(query)}&format=json&engines=google,bing&language=en`;
|
| 681 |
+
const {res: r} = await fetchViaProxyOrDirect(url);
|
| 682 |
+
const d = await parseResponse(r);
|
| 683 |
+
const dd = (typeof d === 'string') ? JSON.parse(d) : d;
|
| 684 |
+
if (dd.results?.length) {
|
| 685 |
+
log(`β SearXNG: ${dd.results.length}`,'g');
|
| 686 |
+
return dd.results.slice(0,5).map((x,i)=>`[${i+1}] ${x.title}\n${x.url}\n${x.content||''}`).join('\n\n');
|
| 687 |
+
}
|
| 688 |
+
} catch(e){ log(`SearXNG: ${e.message}`,'w'); }
|
| 689 |
+
|
| 690 |
+
// 3. DuckDuckGo instant answer API
|
| 691 |
+
try {
|
| 692 |
+
const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`;
|
| 693 |
+
const {res: r} = await fetchViaProxyOrDirect(url);
|
| 694 |
+
const d = await parseResponse(r);
|
| 695 |
+
const dd = (typeof d === 'string') ? (() => { try { return JSON.parse(d); } catch(_) { return {}; } })() : (d || {});
|
| 696 |
+
console.log('[FE] DDG API keys:', Object.keys(dd).filter(k => dd[k] && (typeof dd[k] !== 'object' || (Array.isArray(dd[k]) ? dd[k].length : Object.keys(dd[k]).length))));
|
| 697 |
+
const out = [];
|
| 698 |
+
if (dd.AbstractText) out.push(`Summary: ${dd.AbstractText}\n${dd.AbstractURL}`);
|
| 699 |
+
if (dd.Answer) out.push(`Direct Answer: ${dd.Answer}`);
|
| 700 |
+
if (dd.Definition) out.push(`Definition: ${dd.Definition}\n${dd.DefinitionURL||''}`);
|
| 701 |
+
if (dd.Redirect) out.push(`Redirect: ${dd.Redirect}`);
|
| 702 |
+
(dd.RelatedTopics||[]).slice(0,5).forEach((t,i)=>{
|
| 703 |
+
if (t.Text) out.push(`[${i+1}] ${t.Text}\n${t.FirstURL||''}`);
|
| 704 |
+
// DDG groups topics in sub-arrays
|
| 705 |
+
if (t.Topics) t.Topics.slice(0,2).forEach(st => { if(st.Text) out.push(` - ${st.Text}\n ${st.FirstURL||''}`); });
|
| 706 |
+
});
|
| 707 |
+
if (out.length){ log(`β DDG API: ${out.length} items`,'g'); return out.join('\n\n'); }
|
| 708 |
+
} catch(e){ log(`DDG API: ${e.message}`,'e'); }
|
| 709 |
+
|
| 710 |
+
// 4. Fallback β scrape DDG HTML page and extract links from markdown
|
| 711 |
+
try {
|
| 712 |
+
const ddgHtml = `https://duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
| 713 |
+
const {res: r} = await fetchViaProxyOrDirect(ddgHtml);
|
| 714 |
+
const body = await parseResponse(r);
|
| 715 |
+
const bodyStr = (typeof body === 'string') ? body : JSON.stringify(body);
|
| 716 |
+
console.log('[FE] DDG HTML body sample:', bodyStr.slice(0, 400));
|
| 717 |
+
|
| 718 |
+
const results = [];
|
| 719 |
+
let m;
|
| 720 |
+
|
| 721 |
+
// html2text converts DDG HTML links to markdown: [title](//duckduckgo.com/l/?uddg=ENCODED_URL&...)
|
| 722 |
+
// We must: 1) match ALL markdown links (including protocol-relative //) 2) decode uddg= param
|
| 723 |
+
const mdLinkRe = /\[([^\]]+)\]\(([^)]+)\)/g;
|
| 724 |
+
while ((m = mdLinkRe.exec(bodyStr)) && results.length < 10) {
|
| 725 |
+
let title = m[1].trim();
|
| 726 |
+
let url = m[2].trim();
|
| 727 |
+
|
| 728 |
+
// DDG redirect: extract actual URL from uddg= parameter
|
| 729 |
+
if (url.includes('uddg=')) {
|
| 730 |
+
const uddg = url.match(/uddg=([^&]+)/);
|
| 731 |
+
if (uddg) url = decodeURIComponent(uddg[1]);
|
| 732 |
+
}
|
| 733 |
+
|
| 734 |
+
// Skip internal DDG nav links, icons, short titles
|
| 735 |
+
if (!url.startsWith('http')) continue;
|
| 736 |
+
if (url.includes('duckduckgo.com')) continue;
|
| 737 |
+
if (title.length < 3) continue;
|
| 738 |
+
if (results.some(r => r.url === url)) continue;
|
| 739 |
+
|
| 740 |
+
// Grab snippet: next non-empty line after the link in the markdown
|
| 741 |
+
const linkEnd = m.index + m[0].length;
|
| 742 |
+
const after = bodyStr.slice(linkEnd, linkEnd + 300).split('\n').filter(l => l.trim().length > 10);
|
| 743 |
+
const snippet = (after[0] || '').trim().slice(0, 200);
|
| 744 |
+
|
| 745 |
+
results.push({ title, url, snippet });
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
// Also find bare https:// URLs in text
|
| 749 |
+
const bareRe = /(?:^|\s)(https?:\/\/[^\s)<>"]+)/g;
|
| 750 |
+
while ((m = bareRe.exec(bodyStr)) && results.length < 10) {
|
| 751 |
+
const url = m[1];
|
| 752 |
+
if (!url.includes('duckduckgo.com') && !results.some(r => r.url === url)) {
|
| 753 |
+
results.push({ title: url, url, snippet: '' });
|
| 754 |
+
}
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
if (results.length) {
|
| 758 |
+
log(`β DDG HTML fallback: ${results.length} results`,'g');
|
| 759 |
+
const list = results.slice(0,6).map((r,i) =>
|
| 760 |
+
`[${i+1}] ${r.title}\n${r.url}${r.snippet ? '\n' + r.snippet : ''}`
|
| 761 |
+
).join('\n\n');
|
| 762 |
+
// Scrape top result for richer content
|
| 763 |
+
let topContent = '';
|
| 764 |
+
try { topContent = await toolScrape(results[0].url); }
|
| 765 |
+
catch(e) { topContent = '(scrape of top result failed)'; }
|
| 766 |
+
return `Search results for "${query}":\n${list}\n\n---\nTop result content:\n${topContent}`;
|
| 767 |
+
}
|
| 768 |
+
} catch(e){ log(`DDG-html: ${e.message}`,'w'); }
|
| 769 |
+
|
| 770 |
+
return `No results found for: "${query}"\nTip: Add a free Brave Search API key in Settings.`;
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
async function toolScrape(url) {
|
| 774 |
+
stats.scr++; log(`π scrape: ${url}`,'t');
|
| 775 |
+
try {
|
| 776 |
+
const {res: r, viaProxy} = await fetchViaProxyOrDirect(url);
|
| 777 |
+
let text = await parseResponse(r);
|
| 778 |
+
if (typeof text !== 'string') text = JSON.stringify(text);
|
| 779 |
+
|
| 780 |
+
// If proxy returned markdown (content-type: text/markdown), use it as-is
|
| 781 |
+
const ct = r.headers?.get('content-type') || '';
|
| 782 |
+
if (!viaProxy || !ct.includes('markdown')) {
|
| 783 |
+
// Strip HTML tags for non-markdown responses
|
| 784 |
+
text = text
|
| 785 |
+
.replace(/<script[\s\S]*?<\/script>/gi,'')
|
| 786 |
+
.replace(/<style[\s\S]*?<\/style>/gi,'')
|
| 787 |
+
.replace(/<nav[\s\S]*?<\/nav>/gi,'')
|
| 788 |
+
.replace(/<header[\s\S]*?<\/header>/gi,'')
|
| 789 |
+
.replace(/<footer[\s\S]*?<\/footer>/gi,'')
|
| 790 |
+
.replace(/<[^>]+>/g,' ')
|
| 791 |
+
.replace(/ /g,' ').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')
|
| 792 |
+
.replace(/[ \t]{3,}/g,' ').replace(/\n{3,}/g,'\n\n').trim();
|
| 793 |
+
}
|
| 794 |
+
if (text.length < 80) return `No readable content at ${url}`;
|
| 795 |
+
log(`β scraped ${text.length} chars`,'g');
|
| 796 |
+
const MAX=7000;
|
| 797 |
+
return text.slice(0,MAX) + (text.length>MAX ? `\n\n[...truncated β use summarize() for key points]` : '');
|
| 798 |
+
} catch(e){ log(`scrape: ${e.message}`,'e'); return `Failed to scrape ${url}: ${e.message}`; }
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
async function toolSummarize(text, focus) {
|
| 802 |
+
if (!engine) return 'No engine loaded.';
|
| 803 |
+
log(`π summarize ${text.length} chars`+(focus?` focus:${focus}`:''),'t');
|
| 804 |
+
const r = await engine.chat.completions.create({
|
| 805 |
+
messages:[{role:'user',content: focus
|
| 806 |
+
? `Summarize focusing on "${focus}":\n\n${text.slice(0,9000)}`
|
| 807 |
+
: `Write a concise summary of key points:\n\n${text.slice(0,9000)}`
|
| 808 |
+
}], max_tokens:600, temperature:0.2
|
| 809 |
+
});
|
| 810 |
+
const s = r.choices[0].message.content;
|
| 811 |
+
log(`β summarized β ${s.length} chars`,'g');
|
| 812 |
+
return s;
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
function toolRemember(content) {
|
| 816 |
+
const date = new Date().toLocaleDateString('en-DE',{year:'numeric',month:'short',day:'numeric'});
|
| 817 |
+
if (mem.user === DEFAULT_USER) mem.user = '# User Memory\n';
|
| 818 |
+
mem.user += `\n\n## Note β ${date}\n${content}`;
|
| 819 |
+
lss('user', mem.user);
|
| 820 |
+
refreshFilesUI(); refreshMemView();
|
| 821 |
+
log(`πΎ remembered: "${content.slice(0,60)}"`, 'g');
|
| 822 |
+
return `β Saved to user memory: "${content.slice(0,80)}${content.length>80?'β¦':''}"`;
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
function toolReadMemory() {
|
| 826 |
+
log('π read_memory','t');
|
| 827 |
+
return mem.user || '(empty)';
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
function toolForget(query) {
|
| 831 |
+
log(`π forget: "${query}"`,'t');
|
| 832 |
+
const lines = mem.user.split('\n');
|
| 833 |
+
const filtered = [];
|
| 834 |
+
let skip = false;
|
| 835 |
+
for (const line of lines) {
|
| 836 |
+
if (line.startsWith('## ') && line.toLowerCase().includes(query.toLowerCase())) {
|
| 837 |
+
skip = true; continue;
|
| 838 |
+
}
|
| 839 |
+
if (line.startsWith('## ') && skip) skip = false;
|
| 840 |
+
if (!skip) filtered.push(line);
|
| 841 |
+
}
|
| 842 |
+
const newMem = filtered.join('\n');
|
| 843 |
+
if (newMem === mem.user) return `No memory entries matching "${query}" found.`;
|
| 844 |
+
mem.user = newMem;
|
| 845 |
+
lss('user', mem.user);
|
| 846 |
+
refreshFilesUI(); refreshMemView();
|
| 847 |
+
log(`β forgot entries matching "${query}"`,'g');
|
| 848 |
+
return `β Removed memory entries matching "${query}".`;
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
function toolSchedule(delaySec, message) {
|
| 852 |
+
const delayMs = Math.max(1, Math.min(86400, delaySec)) * 1000;
|
| 853 |
+
const fireAt = Date.now() + delayMs;
|
| 854 |
+
const id = 'sched_' + Date.now();
|
| 855 |
+
const task = { id, message, fireAt, delaySec };
|
| 856 |
+
scheduledTasks.push(task);
|
| 857 |
+
lss('sched', scheduledTasks);
|
| 858 |
+
refreshSchedUI();
|
| 859 |
+
|
| 860 |
+
setTimeout(() => {
|
| 861 |
+
showToast(`β° ${message}`);
|
| 862 |
+
log(`β° SCHEDULED FIRED: ${message}`,'w');
|
| 863 |
+
scheduledTasks = scheduledTasks.filter(t => t.id !== id);
|
| 864 |
+
lss('sched', scheduledTasks);
|
| 865 |
+
refreshSchedUI();
|
| 866 |
+
if (engine && !isRunning) {
|
| 867 |
+
runAgent(`[SCHEDULED REMINDER] ${message}`);
|
| 868 |
+
}
|
| 869 |
+
}, delayMs);
|
| 870 |
+
|
| 871 |
+
log(`β° scheduled in ${delaySec}s: "${message}"`,'g');
|
| 872 |
+
return `β Scheduled notification in ${delaySec} seconds: "${message}"`;
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
function toolInjectJs(code, description) {
|
| 876 |
+
log(`π inject_js: ${description||'(no desc)'}`, 't');
|
| 877 |
+
// Validate β block dangerous patterns
|
| 878 |
+
const banned = [
|
| 879 |
+
/\beval\b/, /\bFunction\s*\(/, /document\.cookie/i,
|
| 880 |
+
/localStorage\.clear/i, /window\.location\s*=/,
|
| 881 |
+
/importScripts/i,
|
| 882 |
+
];
|
| 883 |
+
for (const pat of banned) {
|
| 884 |
+
if (pat.test(code)) return `β Blocked: code matches forbidden pattern ${pat}. Injection refused.`;
|
| 885 |
+
}
|
| 886 |
+
if (code.length > 5000) return 'β Code too long (max 5000 chars).';
|
| 887 |
+
try {
|
| 888 |
+
const result = new Function('log', 'document', 'window',
|
| 889 |
+
`"use strict";\ntry {\n${code}\nreturn "β Executed successfully";\n} catch(e) { return "Error: " + e.message; }`
|
| 890 |
+
)(log, document, window);
|
| 891 |
+
log(`β inject_js ok: ${String(result).slice(0,100)}`,'g');
|
| 892 |
+
return String(result);
|
| 893 |
+
} catch(e) {
|
| 894 |
+
log(`inject_js error: ${e.message}`,'e');
|
| 895 |
+
return `β Execution error: ${e.message}`;
|
| 896 |
+
}
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
async function toolNetworkScan() {
|
| 900 |
+
log('π‘ network_scan startingβ¦','t');
|
| 901 |
+
const targets = [
|
| 902 |
+
{ url: 'http://127.0.0.1:8080', label: 'proxy (8080)' },
|
| 903 |
+
{ url: 'http://127.0.0.1:8000', label: 'http (8000)' },
|
| 904 |
+
{ url: 'http://127.0.0.1:3000', label: 'dev (3000)' },
|
| 905 |
+
{ url: 'http://127.0.0.1:3001', label: 'dev (3001)' },
|
| 906 |
+
{ url: 'http://127.0.0.1:5000', label: 'flask (5000)' },
|
| 907 |
+
{ url: 'http://127.0.0.1:5173', label: 'vite (5173)' },
|
| 908 |
+
{ url: 'http://127.0.0.1:4200', label: 'angular (4200)' },
|
| 909 |
+
{ url: 'http://127.0.0.1:8888', label: 'jupyter (8888)' },
|
| 910 |
+
{ url: 'http://127.0.0.1:9090', label: 'prometheus (9090)' },
|
| 911 |
+
{ url: 'http://127.0.0.1:11434', label: 'ollama (11434)' },
|
| 912 |
+
];
|
| 913 |
+
const results = await Promise.allSettled(
|
| 914 |
+
targets.map(async t => {
|
| 915 |
+
try {
|
| 916 |
+
const r = await ft(t.url, {mode:'no-cors'}, 3000);
|
| 917 |
+
return { ...t, status: 'UP', code: r.status || 'opaque' };
|
| 918 |
+
} catch(e) {
|
| 919 |
+
return { ...t, status: 'DOWN' };
|
| 920 |
+
}
|
| 921 |
+
})
|
| 922 |
+
);
|
| 923 |
+
networkHosts = results.map(r => r.value || r.reason).filter(Boolean);
|
| 924 |
+
refreshNetUI();
|
| 925 |
+
const up = networkHosts.filter(h => h.status === 'UP');
|
| 926 |
+
log(`π‘ scan: ${up.length}/${targets.length} up`,'g');
|
| 927 |
+
return `Network scan (${new Date().toLocaleTimeString()}):\n` +
|
| 928 |
+
networkHosts.map(h => `${h.status === 'UP' ? 'β' : 'β'} ${h.label} β ${h.url}`).join('\n');
|
| 929 |
+
}
|
| 930 |
+
|
| 931 |
+
function toolRagIndex(text, source) {
|
| 932 |
+
if (!text || text.trim().length < 20) return 'Error: text too short to index (min 20 chars).';
|
| 933 |
+
const label = source || 'tool-input-' + Date.now();
|
| 934 |
+
const result = ragEngine.addText(text);
|
| 935 |
+
ragSources.push(label);
|
| 936 |
+
refreshRagUI();
|
| 937 |
+
log(`π RAG indexed: ${result.sentences} sentences, ${result.uniqueTerms} terms (${label})`, 'g');
|
| 938 |
+
return `β RAG indexed: ${result.sentences} sentences, ${result.uniqueTerms} unique terms. Source: ${label}`;
|
| 939 |
+
}
|
| 940 |
+
|
| 941 |
+
function toolRagSearch(query, topK) {
|
| 942 |
+
if (!ragEngine.indexed) return 'No text indexed yet. Use rag_index to add documents first.';
|
| 943 |
+
const k = Math.min(Math.max(1, topK || 5), 10);
|
| 944 |
+
const result = ragEngine.query(query, k, 1);
|
| 945 |
+
log(`π RAG search: "${query}" β ${result.passages.length} passages (${result.totalCandidates} candidates)`, 'g');
|
| 946 |
+
if (result.passages.length === 0) return `No relevant passages found for: "${query}"`;
|
| 947 |
+
refreshRagUI();
|
| 948 |
+
return result.passages.map((p, i) =>
|
| 949 |
+
`[${i + 1}] (score: ${p.score.toFixed(2)}) ${p.text}`
|
| 950 |
+
).join('\n\n');
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
+
function toolRagPrompt(query, topK) {
|
| 954 |
+
if (!ragEngine.indexed) return 'No text indexed yet. Use rag_index first.';
|
| 955 |
+
const result = ragEngine.query(query, topK || 5, 1);
|
| 956 |
+
log(`π RAG prompt built for: "${query}"`, 'g');
|
| 957 |
+
return result.prompt;
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
async function runTool(name, params) {
|
| 961 |
+
switch(name) {
|
| 962 |
+
case 'web_search': return await toolSearch(params.query||params.q||'');
|
| 963 |
+
case 'scrape': return await toolScrape(params.url||'');
|
| 964 |
+
case 'summarize': return await toolSummarize(params.text||'', params.focus||'');
|
| 965 |
+
case 'remember': return toolRemember(params.content||params.text||'');
|
| 966 |
+
case 'read_memory': return toolReadMemory();
|
| 967 |
+
case 'forget': return toolForget(params.query||params.content||'');
|
| 968 |
+
case 'schedule': return toolSchedule(Number(params.delay_sec||params.delay||60), params.message||params.content||'Reminder');
|
| 969 |
+
case 'inject_js': return toolInjectJs(params.code||'', params.description||'');
|
| 970 |
+
case 'network_scan': return await toolNetworkScan();
|
| 971 |
+
case 'rag_index': return toolRagIndex(params.text||params.content||'', params.source||params.label||'');
|
| 972 |
+
case 'rag_search': return toolRagSearch(params.query||params.q||'', Number(params.top_k||params.topK||5));
|
| 973 |
+
case 'rag_prompt': return toolRagPrompt(params.query||params.q||'', Number(params.top_k||params.topK||5));
|
| 974 |
+
default: return `Unknown tool "${name}". Available: web_search, scrape, summarize, remember, read_memory, forget, schedule, inject_js, network_scan, rag_index, rag_search, rag_prompt`;
|
| 975 |
+
}
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 979 |
+
// AGENT LOOP
|
| 980 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 981 |
+
async function runAgent(msg) {
|
| 982 |
+
if (isRunning||!engine) return;
|
| 983 |
+
isRunning=true; abortCtrl=new AbortController();
|
| 984 |
+
document.getElementById('sbtn').style.display='none';
|
| 985 |
+
document.getElementById('stopbtn').style.display='block';
|
| 986 |
+
document.getElementById('uin').disabled=true;
|
| 987 |
+
|
| 988 |
+
// Save to input history
|
| 989 |
+
if (msg && !msg.startsWith('[SCHEDULED')) {
|
| 990 |
+
inputHistory = inputHistory.filter(h => h !== msg);
|
| 991 |
+
inputHistory.unshift(msg);
|
| 992 |
+
if (inputHistory.length > 50) inputHistory.pop();
|
| 993 |
+
lss('inputHist', inputHistory);
|
| 994 |
+
}
|
| 995 |
+
historyIdx = -1;
|
| 996 |
+
|
| 997 |
+
appendUser(msg);
|
| 998 |
+
chatHistory.push({role:'user',content:msg});
|
| 999 |
+
stats.msgs++;
|
| 1000 |
+
const scrapeBefore = stats.scr, searchBefore = stats.srch;
|
| 1001 |
+
|
| 1002 |
+
const apiMsgs = [
|
| 1003 |
+
{role:'system', content:buildSysPrompt()},
|
| 1004 |
+
...chatHistory.slice(-18)
|
| 1005 |
+
];
|
| 1006 |
+
|
| 1007 |
+
const agentEl = createAgentBubble();
|
| 1008 |
+
let iter=0, lastText='';
|
| 1009 |
+
|
| 1010 |
+
try {
|
| 1011 |
+
while (iter < MAX_ITER) {
|
| 1012 |
+
iter++; stats.iter++;
|
| 1013 |
+
document.getElementById('iter-p').textContent = `ITER ${iter}`;
|
| 1014 |
+
log(`ββ iter ${iter}/${MAX_ITER}`,'');
|
| 1015 |
+
|
| 1016 |
+
const {text, tps} = await streamLLM(apiMsgs, agentEl);
|
| 1017 |
+
lastText=text; updateTPS(tps); updateStats();
|
| 1018 |
+
|
| 1019 |
+
const am = text.match(/<action>([\s\S]*?)<\/action>/);
|
| 1020 |
+
if (!am) {
|
| 1021 |
+
renderAnswer(agentEl, text);
|
| 1022 |
+
chatHistory.push({role:'assistant',content:text});
|
| 1023 |
+
stats.msgs++;
|
| 1024 |
+
break;
|
| 1025 |
+
}
|
| 1026 |
+
|
| 1027 |
+
let tc;
|
| 1028 |
+
try { tc = JSON.parse(am[1].trim()); }
|
| 1029 |
+
catch(e) {
|
| 1030 |
+
addToolCard(agentEl,{tool:'parse_error'},null,`JSON error: ${e.message}`,true);
|
| 1031 |
+
apiMsgs.push({role:'assistant',content:text});
|
| 1032 |
+
apiMsgs.push({role:'user',content:`<observation>ERROR: could not parse action JSON: ${e.message}\nRaw: ${am[1]}</observation>`});
|
| 1033 |
+
continue;
|
| 1034 |
+
}
|
| 1035 |
+
|
| 1036 |
+
apiMsgs.push({role:'assistant',content:text});
|
| 1037 |
+
const card = addToolCard(agentEl, tc, null, null, false);
|
| 1038 |
+
let result;
|
| 1039 |
+
try { result = await runTool(tc.tool, tc); }
|
| 1040 |
+
catch(e) { result = `Tool error: ${e.message}`; }
|
| 1041 |
+
finalizeCard(card, result);
|
| 1042 |
+
stats.tok += Math.ceil(result.length/4);
|
| 1043 |
+
apiMsgs.push({role:'user',content:`<observation tool="${tc.tool}">\n${result}\n</observation>`});
|
| 1044 |
+
}
|
| 1045 |
+
if (iter>=MAX_ITER) { addNote(agentEl,`Max iterations (${MAX_ITER}) reached.`); chatHistory.push({role:'assistant',content:lastText}); }
|
| 1046 |
+
|
| 1047 |
+
} catch(err) {
|
| 1048 |
+
if (err.name==='AbortError') addNote(agentEl,'β Stopped.');
|
| 1049 |
+
else { addNote(agentEl,`Error: ${err.message}`); log(err.message,'e'); }
|
| 1050 |
+
}
|
| 1051 |
+
|
| 1052 |
+
isRunning=false; abortCtrl=null;
|
| 1053 |
+
document.getElementById('iter-p').textContent='ITER 0';
|
| 1054 |
+
document.getElementById('sbtn').style.display='block';
|
| 1055 |
+
document.getElementById('stopbtn').style.display='none';
|
| 1056 |
+
document.getElementById('uin').disabled=false;
|
| 1057 |
+
document.getElementById('uin').focus();
|
| 1058 |
+
updateStats();
|
| 1059 |
+
validateResponse(msg, lastText, stats.scr - scrapeBefore, stats.srch - searchBefore);
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1063 |
+
// RESPONSE VALIDATOR
|
| 1064 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1065 |
+
function validateResponse(userMsg, agentText, scrapesDone, searchesDone) {
|
| 1066 |
+
const warnings = [];
|
| 1067 |
+
const urlRe = /https?:\/\/[^\s)>\"']+/gi;
|
| 1068 |
+
const mentionedURLs = userMsg.match(urlRe) || [];
|
| 1069 |
+
const opens = (agentText.match(/<action>/g) || []).length;
|
| 1070 |
+
const closes = (agentText.match(/<\/action>/g) || []).length;
|
| 1071 |
+
if (opens !== closes) warnings.push('β Malformed <action> tags (mismatched open/close).');
|
| 1072 |
+
|
| 1073 |
+
const intendedScrape = /"tool"\s*:\s*"scrape"/.test(agentText);
|
| 1074 |
+
const intendedSearch = /"tool"\s*:\s*"web_search"/.test(agentText);
|
| 1075 |
+
|
| 1076 |
+
if (mentionedURLs.length > 0 && scrapesDone === 0 && !intendedScrape)
|
| 1077 |
+
warnings.push(`β URL detected but scrape() was not called.`);
|
| 1078 |
+
if (mentionedURLs.length > 0 && scrapesDone === 0 && intendedScrape)
|
| 1079 |
+
warnings.push('β Agent tried to scrape but it may have failed.');
|
| 1080 |
+
|
| 1081 |
+
const needsSearch = /\b(latest|current|today|news|recent|who is|what is the price|202[0-9])\b/i.test(userMsg);
|
| 1082 |
+
if (needsSearch && searchesDone === 0 && !intendedSearch && scrapesDone === 0)
|
| 1083 |
+
warnings.push('β Time-sensitive query β no search was used.');
|
| 1084 |
+
|
| 1085 |
+
if (agentText && agentText.length < 60 && opens === 0)
|
| 1086 |
+
warnings.push('β Very short response β model may have truncated.');
|
| 1087 |
+
|
| 1088 |
+
if (warnings.length > 0) {
|
| 1089 |
+
const el = createAgentBubble();
|
| 1090 |
+
el.closest('.mwrap').querySelector('.mrole').textContent = 'HINT';
|
| 1091 |
+
el.closest('.mwrap').querySelector('.mrole').style.color = 'var(--amber)';
|
| 1092 |
+
warnings.forEach(w => addNote(el, w));
|
| 1093 |
+
}
|
| 1094 |
+
}
|
| 1095 |
+
|
| 1096 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1097 |
+
// STREAMING
|
| 1098 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1099 |
+
async function streamLLM(msgs, container) {
|
| 1100 |
+
const d = document.createElement('div');
|
| 1101 |
+
d.style.cssText='font-size:11px;color:var(--fg-dim);white-space:pre-wrap;line-height:1.5;border-left:2px solid var(--fg-faint);padding-left:8px;margin-bottom:4px;min-height:16px';
|
| 1102 |
+
const cur = document.createElement('span'); cur.className='cur';
|
| 1103 |
+
d.appendChild(cur); container.appendChild(d); scroll();
|
| 1104 |
+
|
| 1105 |
+
let full='', toks=0;
|
| 1106 |
+
const t0=Date.now();
|
| 1107 |
+
const stream = await engine.chat.completions.create({
|
| 1108 |
+
messages:msgs, stream:true, temperature:0.7, max_tokens:1400,
|
| 1109 |
+
stream_options:{include_usage:true}
|
| 1110 |
+
});
|
| 1111 |
+
|
| 1112 |
+
for await (const chunk of stream) {
|
| 1113 |
+
if (abortCtrl?.signal.aborted) break;
|
| 1114 |
+
const delta = chunk.choices[0]?.delta?.content||'';
|
| 1115 |
+
if (delta) { full+=delta; toks++; stats.tok++; d.innerHTML=esc(full).replace(/\n/g,'<br>'); d.appendChild(cur); scroll(); }
|
| 1116 |
+
}
|
| 1117 |
+
cur.remove(); d.remove();
|
| 1118 |
+
const secs=(Date.now()-t0)/1000;
|
| 1119 |
+
log(`β ${toks} tok Β· ${secs>0?Math.round(toks/secs):0}t/s`,'g');
|
| 1120 |
+
return {text:full, tps:secs>0?Math.round(toks/secs):0};
|
| 1121 |
+
}
|
| 1122 |
+
|
| 1123 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1124 |
+
// RENDER
|
| 1125 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1126 |
+
function appendUser(text) {
|
| 1127 |
+
const w=document.createElement('div'); w.className='mwrap user';
|
| 1128 |
+
w.innerHTML=`<div class="mrole">USER</div><div class="mbody">${esc(text).replace(/\n/g,'<br>')}</div>`;
|
| 1129 |
+
document.getElementById('msgs').appendChild(w); scroll();
|
| 1130 |
+
}
|
| 1131 |
+
function createAgentBubble() {
|
| 1132 |
+
const w=document.createElement('div'); w.className='mwrap agent';
|
| 1133 |
+
const id='ac'+Date.now();
|
| 1134 |
+
w.innerHTML=`<div class="mrole">AGENT</div><div class="mbody" id="${id}"></div>`;
|
| 1135 |
+
document.getElementById('msgs').appendChild(w); scroll();
|
| 1136 |
+
return document.getElementById(id);
|
| 1137 |
+
}
|
| 1138 |
+
function renderAnswer(container, text) {
|
| 1139 |
+
const thRe=/<think>([\s\S]*?)<\/think>/g; let m;
|
| 1140 |
+
while((m=thRe.exec(text))!==null) {
|
| 1141 |
+
const tb=document.createElement('div'); tb.className='think';
|
| 1142 |
+
tb.innerHTML=`<div class="think-hdr"><span>π</span><span>Thinkingβ¦</span><span class="think-toggle">βΌ expand</span></div><div class="think-body">${esc(m[1].trim()).replace(/\n/g,'<br>')}</div>`;
|
| 1143 |
+
tb.querySelector('.think-hdr').addEventListener('click',()=>{
|
| 1144 |
+
tb.classList.toggle('open');
|
| 1145 |
+
tb.querySelector('.think-toggle').textContent=tb.classList.contains('open')?'β² collapse':'βΌ expand';
|
| 1146 |
+
});
|
| 1147 |
+
container.appendChild(tb);
|
| 1148 |
+
}
|
| 1149 |
+
const clean = text.replace(/<think>[\s\S]*?<\/think>/g,'').replace(/<action>[\s\S]*?<\/action>/g,'').trim();
|
| 1150 |
+
if (clean) { const a=document.createElement('div'); a.className='answer'; a.innerHTML=md(clean); container.appendChild(a); }
|
| 1151 |
+
scroll();
|
| 1152 |
+
}
|
| 1153 |
+
|
| 1154 |
+
const TICONS={web_search:'π',scrape:'π',summarize:'π',remember:'πΎ',read_memory:'π',forget:'π',schedule:'β°',inject_js:'π',network_scan:'π‘',rag_index:'π',rag_search:'π',rag_prompt:'π',parse_error:'β '};
|
| 1155 |
+
|
| 1156 |
+
function addToolCard(container, tc, result, errMsg, isError) {
|
| 1157 |
+
const card=document.createElement('div'); card.className='tcard';
|
| 1158 |
+
if(isError) card.style.borderColor='var(--red)';
|
| 1159 |
+
const params=Object.entries(tc).filter(([k])=>k!=='tool').map(([k,v])=>`${k}="${esc(String(v).slice(0,70))}"`).join(' ');
|
| 1160 |
+
const sid='ts'+Date.now();
|
| 1161 |
+
card.innerHTML=`
|
| 1162 |
+
<div class="tcard-hdr" onclick="this.nextElementSibling.classList.toggle('vis')">
|
| 1163 |
+
<span>${TICONS[tc.tool]||'π§'}</span>
|
| 1164 |
+
<span class="tname">${esc(tc.tool||'')}</span>
|
| 1165 |
+
<span class="tparams">${params}</span>
|
| 1166 |
+
<span id="${sid}" class="${isError?'':'tspin'}">${isError?'β ':'β³'}</span>
|
| 1167 |
+
<span class="ttog">βΎ</span>
|
| 1168 |
+
</div>
|
| 1169 |
+
<div class="tres${result||errMsg?' vis':''}">${esc(errMsg||result||'runningβ¦')}</div>`;
|
| 1170 |
+
container.appendChild(card); scroll();
|
| 1171 |
+
return card;
|
| 1172 |
+
}
|
| 1173 |
+
function finalizeCard(card, result) {
|
| 1174 |
+
const sp=card.querySelector('.tspin');
|
| 1175 |
+
if(sp){sp.style.animation='none';sp.textContent='β';sp.style.color='var(--fg)';}
|
| 1176 |
+
const res=card.querySelector('.tres');
|
| 1177 |
+
if(res){res.textContent=result;res.classList.add('vis');}
|
| 1178 |
+
scroll();
|
| 1179 |
+
}
|
| 1180 |
+
function addNote(container, text) {
|
| 1181 |
+
const d=document.createElement('div');
|
| 1182 |
+
d.style.cssText='font-size:10px;color:var(--fg-dim);border-left:2px solid var(--border);padding-left:6px;margin-top:4px;font-style:italic';
|
| 1183 |
+
d.textContent=text; container.appendChild(d); scroll();
|
| 1184 |
+
}
|
| 1185 |
+
function sysMsg(text) {
|
| 1186 |
+
const w=document.createElement('div'); w.className='mwrap sys';
|
| 1187 |
+
w.innerHTML=`<div class="mrole">SYS</div><div class="mbody">${esc(text).replace(/\n/g,'<br>')}</div>`;
|
| 1188 |
+
document.getElementById('msgs').appendChild(w); scroll();
|
| 1189 |
+
}
|
| 1190 |
+
|
| 1191 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1192 |
+
// MARKDOWN / UTILS
|
| 1193 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1194 |
+
function md(text) {
|
| 1195 |
+
let h=esc(text);
|
| 1196 |
+
h=h.replace(/```(\w*)\n?([\s\S]*?)```/g,(_,l,c)=>`<pre><code>${c}</code></pre>`);
|
| 1197 |
+
h=h.replace(/`([^`\n]+)`/g,'<code>$1</code>');
|
| 1198 |
+
h=h.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');
|
| 1199 |
+
h=h.replace(/\*([^*\n]+)\*/g,'<em>$1</em>');
|
| 1200 |
+
h=h.replace(/^### (.+)$/gm,'<h3>$1</h3>');
|
| 1201 |
+
h=h.replace(/^## (.+)$/gm,'<h2>$1</h2>');
|
| 1202 |
+
h=h.replace(/^# (.+)$/gm,'<h1>$1</h1>');
|
| 1203 |
+
h=h.replace(/^[-*β’] (.+)$/gm,'<li>$1</li>');
|
| 1204 |
+
h=h.replace(/^\d+\. (.+)$/gm,'<li>$1</li>');
|
| 1205 |
+
h=h.replace(/^---$/gm,'<hr style="border:none;border-top:1px solid var(--border);margin:8px 0">');
|
| 1206 |
+
h=h.replace(/\n\n/g,'<br><br>').replace(/\n(?!<)/g,'<br>');
|
| 1207 |
+
return h;
|
| 1208 |
+
}
|
| 1209 |
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
| 1210 |
+
function scroll(){const m=document.getElementById('msgs');m.scrollTop=m.scrollHeight;}
|
| 1211 |
+
|
| 1212 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1213 |
+
// TOAST NOTIFICATIONS
|
| 1214 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1215 |
+
function showToast(msg, duration=6000) {
|
| 1216 |
+
const area = document.getElementById('toast-area');
|
| 1217 |
+
const t = document.createElement('div'); t.className = 'toast';
|
| 1218 |
+
t.textContent = msg;
|
| 1219 |
+
area.appendChild(t);
|
| 1220 |
+
// Flash the header too
|
| 1221 |
+
const hdr = document.getElementById('hdr');
|
| 1222 |
+
hdr.style.borderBottom = '2px solid var(--amber)';
|
| 1223 |
+
setTimeout(() => { hdr.style.borderBottom = ''; }, 2000);
|
| 1224 |
+
setTimeout(() => { t.classList.add('fade'); setTimeout(() => t.remove(), 500); }, duration);
|
| 1225 |
+
}
|
| 1226 |
+
|
| 1227 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1228 |
+
// FILE MANAGEMENT
|
| 1229 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1230 |
+
window.triggerFile = t => document.getElementById(t==='skill'?'skill-f':t+'-f').click();
|
| 1231 |
+
function readFile(file, cb) { const r=new FileReader(); r.onload=e=>cb(e.target.result,file.name); r.readAsText(file); }
|
| 1232 |
+
|
| 1233 |
+
document.getElementById('soul-f').addEventListener('change',function(){
|
| 1234 |
+
if(this.files[0]) readFile(this.files[0],(c,n)=>{mem.soul=c;lss('soul',c);refreshFilesUI();refreshMemView();log(`β soul.md loaded (${c.length}ch)`,'g');});
|
| 1235 |
+
});
|
| 1236 |
+
document.getElementById('user-f').addEventListener('change',function(){
|
| 1237 |
+
if(this.files[0]) readFile(this.files[0],(c,n)=>{mem.user=c;lss('user',c);refreshFilesUI();refreshMemView();log(`β user.md loaded (${c.length}ch)`,'g');});
|
| 1238 |
+
});
|
| 1239 |
+
document.getElementById('skill-f').addEventListener('change',function(){
|
| 1240 |
+
Array.from(this.files).forEach(f=>readFile(f,(c,n)=>{
|
| 1241 |
+
const k=n.replace(/\.(md|txt)$/i,'');
|
| 1242 |
+
mem.skills[k]=c; lss('skills',mem.skills);
|
| 1243 |
+
refreshFilesUI(); refreshMemView();
|
| 1244 |
+
log(`β skill: ${k} loaded`,'g');
|
| 1245 |
+
}));
|
| 1246 |
+
});
|
| 1247 |
+
|
| 1248 |
+
window.removeSkill = function(name) {
|
| 1249 |
+
delete mem.skills[name]; lss('skills',mem.skills);
|
| 1250 |
+
refreshFilesUI(); refreshMemView(); log(`removed skill: ${name}`,'w');
|
| 1251 |
+
};
|
| 1252 |
+
|
| 1253 |
+
function refreshFilesUI() {
|
| 1254 |
+
const sc=mem.soul!==DEFAULT_SOUL, uc=mem.user!==DEFAULT_USER;
|
| 1255 |
+
document.getElementById('soul-n').className='fname'+(sc?' ok':'');
|
| 1256 |
+
document.getElementById('soul-b').textContent=sc?mem.soul.length+'ch':'default';
|
| 1257 |
+
document.getElementById('user-n').className='fname'+(uc?' ok':'');
|
| 1258 |
+
document.getElementById('user-b').textContent=uc?mem.user.length+'ch':'default';
|
| 1259 |
+
const chips=document.getElementById('skill-chips');
|
| 1260 |
+
chips.innerHTML='';
|
| 1261 |
+
Object.keys(mem.skills).forEach(name=>{
|
| 1262 |
+
const c=document.createElement('div'); c.className='chip';
|
| 1263 |
+
c.innerHTML=`${esc(name)} <span class="rm" onclick="removeSkill('${esc(name)}')">β</span>`;
|
| 1264 |
+
chips.appendChild(c);
|
| 1265 |
+
});
|
| 1266 |
+
}
|
| 1267 |
+
|
| 1268 |
+
function refreshMemView() {
|
| 1269 |
+
const sp=buildSysPrompt();
|
| 1270 |
+
document.getElementById('mem-view').textContent=
|
| 1271 |
+
`=== LIVE CONTEXT (${sp.length} chars) ===\n\n${sp.slice(0,1400)}${sp.length>1400?'\n\n[β¦truncated β click EDIT to view full]':''}`;
|
| 1272 |
+
}
|
| 1273 |
+
|
| 1274 |
+
window.openEdit = function(target) {
|
| 1275 |
+
editTarget=target;
|
| 1276 |
+
document.getElementById('modal-title').textContent=`EDIT ${target.toUpperCase()}.MD`;
|
| 1277 |
+
document.getElementById('modal-area').value=target==='soul'?mem.soul:mem.user;
|
| 1278 |
+
document.getElementById('modal').classList.add('vis');
|
| 1279 |
+
};
|
| 1280 |
+
window.closeModal = function(){document.getElementById('modal').classList.remove('vis');editTarget=null;};
|
| 1281 |
+
window.saveModal = function(){
|
| 1282 |
+
const v=document.getElementById('modal-area').value;
|
| 1283 |
+
if(editTarget==='soul'){mem.soul=v;lss('soul',v);}
|
| 1284 |
+
if(editTarget==='user'){mem.user=v;lss('user',v);}
|
| 1285 |
+
refreshFilesUI();refreshMemView();closeModal();log(`saved ${editTarget}.md`,'g');
|
| 1286 |
+
};
|
| 1287 |
+
window.exportModal = function(){
|
| 1288 |
+
const v=document.getElementById('modal-area').value;
|
| 1289 |
+
const a=document.createElement('a');
|
| 1290 |
+
a.href=URL.createObjectURL(new Blob([v],{type:'text/markdown'}));
|
| 1291 |
+
a.download=(editTarget||'file')+'.md'; a.click();
|
| 1292 |
+
};
|
| 1293 |
+
document.getElementById('modal').addEventListener('click',e=>{if(e.target===document.getElementById('modal'))closeModal();});
|
| 1294 |
+
|
| 1295 |
+
window.saveCfg = function(){cfg.brave=document.getElementById('brave-in').value.trim();lss('brave',cfg.brave);log('settings saved','g');};
|
| 1296 |
+
window.clearUserMem = function(){if(!confirm('Reset user.md?'))return;mem.user=DEFAULT_USER;lss('user',mem.user);refreshFilesUI();refreshMemView();log('user.md reset','w');};
|
| 1297 |
+
window.exportAll = function(){
|
| 1298 |
+
const out=`# Chronos Export β ${new Date().toISOString().split('T')[0]}\n\n---\n\n# soul.md\n${mem.soul}\n\n---\n\n# user.md\n${mem.user}\n\n---\n\n# Skills\n${Object.entries(mem.skills).map(([n,c])=>`\n## ${n}\n\n${c}`).join('\n')}`;
|
| 1299 |
+
const a=document.createElement('a');
|
| 1300 |
+
a.href=URL.createObjectURL(new Blob([out],{type:'text/markdown'}));
|
| 1301 |
+
a.download=`chronos-export-${Date.now()}.md`; a.click();
|
| 1302 |
+
};
|
| 1303 |
+
window.clearChat = function(){document.getElementById('msgs').innerHTML='';chatHistory=[];stats.msgs=0;stats.iter=0;updateStats();log('chat cleared','w');};
|
| 1304 |
+
|
| 1305 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1306 |
+
// STATS / LOGGING
|
| 1307 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1308 |
+
function log(text,type=''){
|
| 1309 |
+
const el=document.getElementById('alog');
|
| 1310 |
+
const t=new Date().toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'});
|
| 1311 |
+
const d=document.createElement('div'); d.className=`ll ${type}`; d.textContent=`[${t}] ${text}`;
|
| 1312 |
+
el.appendChild(d); el.scrollTop=el.scrollHeight;
|
| 1313 |
+
while(el.children.length>120)el.removeChild(el.firstChild);
|
| 1314 |
+
}
|
| 1315 |
+
function updateTPS(tps){tpsHist.push(tps);if(tpsHist.length>24)tpsHist.shift();document.getElementById('s-tps').innerHTML=`${tps}<span class="su">t/s</span>`;drawSpark();}
|
| 1316 |
+
function updateStats(){document.getElementById('s-tok').textContent=stats.tok;document.getElementById('s-srch').textContent=stats.srch;document.getElementById('s-scr').textContent=stats.scr;document.getElementById('cmeta').textContent=`${stats.msgs} msgs Β· ${stats.tok} tok Β· ${stats.iter} iters`;}
|
| 1317 |
+
function drawSpark(){
|
| 1318 |
+
const c=document.getElementById('spark');if(!c)return;
|
| 1319 |
+
const ctx=c.getContext('2d');const w=c.offsetWidth||180,h=28;c.width=w;
|
| 1320 |
+
if(tpsHist.length<2)return;
|
| 1321 |
+
const max=Math.max(...tpsHist,1);const step=w/(tpsHist.length-1);
|
| 1322 |
+
ctx.clearRect(0,0,w,h);ctx.strokeStyle='#3dff70';ctx.shadowColor='#3dff70';ctx.shadowBlur=4;ctx.lineWidth=1.5;ctx.beginPath();
|
| 1323 |
+
tpsHist.forEach((v,i)=>{const x=i*step,y=h-(v/max)*(h-4)-2;i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});ctx.stroke();
|
| 1324 |
+
ctx.lineTo((tpsHist.length-1)*step,h);ctx.lineTo(0,h);ctx.closePath();ctx.shadowBlur=0;ctx.fillStyle='rgba(61,255,112,0.07)';ctx.fill();
|
| 1325 |
+
}
|
| 1326 |
+
|
| 1327 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1328 |
+
// NETWORK SCAN UI
|
| 1329 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1330 |
+
function refreshNetUI() {
|
| 1331 |
+
const panel = document.getElementById('net-panel');
|
| 1332 |
+
if (!networkHosts.length) { panel.innerHTML = '<div class="meta">No scan yet</div>'; return; }
|
| 1333 |
+
panel.innerHTML = networkHosts.map(h =>
|
| 1334 |
+
`<div class="net-item"><span class="ni-url">${esc(h.label)}</span><span class="ni-status ${h.status==='UP'?'ok':'fail'}">${h.status}</span></div>`
|
| 1335 |
+
).join('');
|
| 1336 |
+
}
|
| 1337 |
+
|
| 1338 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1339 |
+
// SCHEDULER UI
|
| 1340 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1341 |
+
function refreshSchedUI() {
|
| 1342 |
+
const panel = document.getElementById('sched-panel');
|
| 1343 |
+
if (!scheduledTasks.length) { panel.innerHTML = '<div class="meta">No tasks</div>'; return; }
|
| 1344 |
+
panel.innerHTML = scheduledTasks.map(t => {
|
| 1345 |
+
const remaining = Math.max(0, Math.round((t.fireAt - Date.now()) / 1000));
|
| 1346 |
+
return `<div class="sched-item"><span class="si-msg">${esc(t.message)}</span><span class="si-time">${remaining}s</span><span class="si-rm" onclick="cancelSched('${t.id}')">β</span></div>`;
|
| 1347 |
+
}).join('');
|
| 1348 |
+
}
|
| 1349 |
+
window.cancelSched = function(id) {
|
| 1350 |
+
scheduledTasks = scheduledTasks.filter(t => t.id !== id);
|
| 1351 |
+
lss('sched', scheduledTasks);
|
| 1352 |
+
refreshSchedUI();
|
| 1353 |
+
log(`cancelled scheduled task`,'w');
|
| 1354 |
+
};
|
| 1355 |
+
setInterval(refreshSchedUI, 5000);
|
| 1356 |
+
|
| 1357 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1358 |
+
// COMMAND PALETTE (Arrow Up)
|
| 1359 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1360 |
+
function openPalette() {
|
| 1361 |
+
if (paletteOpen) { closePalette(); return; }
|
| 1362 |
+
paletteOpen = true;
|
| 1363 |
+
const pal = document.getElementById('cmd-palette');
|
| 1364 |
+
const inp = document.getElementById('cp-search');
|
| 1365 |
+
const list = document.getElementById('cp-list');
|
| 1366 |
+
pal.classList.add('vis');
|
| 1367 |
+
inp.value = '';
|
| 1368 |
+
inp.focus();
|
| 1369 |
+
renderPaletteItems('');
|
| 1370 |
+
inp.oninput = () => renderPaletteItems(inp.value);
|
| 1371 |
+
inp.onkeydown = function(e) {
|
| 1372 |
+
if (e.key === 'Escape') { closePalette(); return; }
|
| 1373 |
+
if (e.key === 'Enter') {
|
| 1374 |
+
const sel = list.querySelector('.cp-item.sel') || list.querySelector('.cp-item');
|
| 1375 |
+
if (sel) sel.click();
|
| 1376 |
+
}
|
| 1377 |
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
| 1378 |
+
e.preventDefault();
|
| 1379 |
+
const items = [...list.querySelectorAll('.cp-item')];
|
| 1380 |
+
const cur = items.findIndex(i => i.classList.contains('sel'));
|
| 1381 |
+
items.forEach(i => i.classList.remove('sel'));
|
| 1382 |
+
let next = e.key === 'ArrowDown' ? cur + 1 : cur - 1;
|
| 1383 |
+
if (next < 0) next = items.length - 1;
|
| 1384 |
+
if (next >= items.length) next = 0;
|
| 1385 |
+
if (items[next]) { items[next].classList.add('sel'); items[next].scrollIntoView({block:'nearest'}); }
|
| 1386 |
+
}
|
| 1387 |
+
};
|
| 1388 |
+
}
|
| 1389 |
+
function closePalette() {
|
| 1390 |
+
paletteOpen = false;
|
| 1391 |
+
document.getElementById('cmd-palette').classList.remove('vis');
|
| 1392 |
+
document.getElementById('uin').focus();
|
| 1393 |
+
}
|
| 1394 |
+
function renderPaletteItems(filter) {
|
| 1395 |
+
const list = document.getElementById('cp-list');
|
| 1396 |
+
list.innerHTML = '';
|
| 1397 |
+
const items = [];
|
| 1398 |
+
// History
|
| 1399 |
+
inputHistory.forEach(h => items.push({ badge: 'HISTORY', label: h, action: () => { document.getElementById('uin').value = h; closePalette(); } }));
|
| 1400 |
+
// Tools
|
| 1401 |
+
['web_search','scrape','summarize','remember','read_memory','forget','schedule','inject_js','network_scan','rag_index','rag_search','rag_prompt'].forEach(t => {
|
| 1402 |
+
items.push({ badge: 'TOOL', label: t, action: () => { document.getElementById('uin').value = `Use tool: ${t}`; closePalette(); } });
|
| 1403 |
+
});
|
| 1404 |
+
// Skills
|
| 1405 |
+
Object.keys(mem.skills).forEach(s => {
|
| 1406 |
+
items.push({ badge: 'SKILL', label: s, action: () => { document.getElementById('uin').value = `Use skill: ${s}`; closePalette(); } });
|
| 1407 |
+
});
|
| 1408 |
+
// Actions
|
| 1409 |
+
items.push({ badge: 'ACTION', label: 'Clear chat', action: () => { clearChat(); closePalette(); } });
|
| 1410 |
+
items.push({ badge: 'ACTION', label: 'Export all', action: () => { exportAll(); closePalette(); } });
|
| 1411 |
+
items.push({ badge: 'ACTION', label: 'Edit soul.md', action: () => { openEdit('soul'); closePalette(); } });
|
| 1412 |
+
items.push({ badge: 'ACTION', label: 'Edit user.md', action: () => { openEdit('user'); closePalette(); } });
|
| 1413 |
+
items.push({ badge: 'ACTION', label: 'Toggle console', action: () => { toggleConsole(); closePalette(); } });
|
| 1414 |
+
items.push({ badge: 'ACTION', label: 'Network scan', action: async () => { closePalette(); await toolNetworkScan(); } });
|
| 1415 |
+
|
| 1416 |
+
const filt = filter.toLowerCase();
|
| 1417 |
+
const matches = items.filter(i => !filt || i.label.toLowerCase().includes(filt) || i.badge.toLowerCase().includes(filt));
|
| 1418 |
+
matches.slice(0, 20).forEach((item, idx) => {
|
| 1419 |
+
const d = document.createElement('div');
|
| 1420 |
+
d.className = 'cp-item' + (idx === 0 ? ' sel' : '');
|
| 1421 |
+
d.innerHTML = `<span class="cp-badge">${item.badge}</span><span>${esc(item.label)}</span>`;
|
| 1422 |
+
d.addEventListener('click', item.action);
|
| 1423 |
+
list.appendChild(d);
|
| 1424 |
+
});
|
| 1425 |
+
}
|
| 1426 |
+
|
| 1427 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1428 |
+
// MODEL SETUP
|
| 1429 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1430 |
+
const MODELS=[
|
| 1431 |
+
{id:'TinyLlama-1.1B-Chat-v0.4-q4f16_1-MLC',label:'TinyLlama 1.1B Β· ~600MB',vram:'~1GB',speed:'fastest'},
|
| 1432 |
+
{id:'Llama-3.2-1B-Instruct-q4f16_1-MLC',label:'Llama 3.2 Β· 1B Β· ~680MB',vram:'~1GB',speed:'fastest'},
|
| 1433 |
+
{id:'gemma-2-2b-it-q4f16_1-MLC',label:'Gemma 2 Β· 2B Β· ~1.4GB',vram:'~2GB',speed:'fast'},
|
| 1434 |
+
{id:'Llama-3.2-3B-Instruct-q4f16_1-MLC',label:'Llama 3.2 Β· 3B Β· ~1.8GB',vram:'~2GB',speed:'fast'},
|
| 1435 |
+
{id:'Phi-3.5-mini-instruct-q4f16_1-MLC',label:'Phi-3.5 Mini Β· 3.8B Β· ~2.2GB',vram:'~3GB',speed:'fast'},
|
| 1436 |
+
{id:'Llama-3.1-8B-Instruct-q4f16_1-MLC',label:'Llama 3.1 Β· 8B Β· ~4.8GB',vram:'~6GB',speed:'medium'},
|
| 1437 |
+
{id:'Mistral-7B-Instruct-v0.3-q4f16_1-MLC',label:'Mistral Β· 7B Β· ~4.1GB',vram:'~5GB',speed:'medium'},
|
| 1438 |
+
];
|
| 1439 |
+
|
| 1440 |
+
const grps={fastest:'β‘ Ultra-Fast',fast:'π Fast',medium:'β Medium'};
|
| 1441 |
+
const gels={};
|
| 1442 |
+
Object.entries(grps).forEach(([k,l])=>{const g=document.createElement('optgroup');g.label=l;gels[k]=g;document.getElementById('msel').appendChild(g);});
|
| 1443 |
+
MODELS.forEach(m=>{const o=document.createElement('option');o.value=m.id;o.textContent=m.label;o.selected=m.id===cfg.model;gels[m.speed].appendChild(o);});
|
| 1444 |
+
document.getElementById('msel').addEventListener('change',function(){const m=MODELS.find(x=>x.id===this.value);document.getElementById('mmeta').textContent=m?`VRAM: ${m.vram}`:'β';});
|
| 1445 |
+
document.getElementById('msel').dispatchEvent(new Event('change'));
|
| 1446 |
+
|
| 1447 |
+
document.getElementById('lbtn').addEventListener('click', async () => {
|
| 1448 |
+
const modelId = document.getElementById('msel').value;
|
| 1449 |
+
|
| 1450 |
+
if (!navigator.gpu) {
|
| 1451 |
+
log('β WebGPU not available! Use Chrome 113+ or Edge 113+','e');
|
| 1452 |
+
sysMsg('β WebGPU not available.\n\nRequired: Chrome 113+ or Edge 113+\n\nCheck: chrome://gpu');
|
| 1453 |
+
return;
|
| 1454 |
+
}
|
| 1455 |
+
|
| 1456 |
+
// Softer COI check β warn but allow if served via HTTP (proxy sends COOP/COEP headers)
|
| 1457 |
+
if (!window.crossOriginIsolated) {
|
| 1458 |
+
const isHTTP = location.protocol.startsWith('http');
|
| 1459 |
+
if (!isHTTP) {
|
| 1460 |
+
log('β Not cross-origin isolated. Serve via HTTP!','e');
|
| 1461 |
+
document.getElementById('setup-banner').classList.remove('hidden');
|
| 1462 |
+
return;
|
| 1463 |
+
}
|
| 1464 |
+
log('β COI not yet active β attempting load anywayβ¦','w');
|
| 1465 |
+
sysMsg('β Cross-origin isolation pending.\nIf load fails, reload page once (service worker activates on first load).');
|
| 1466 |
+
}
|
| 1467 |
+
|
| 1468 |
+
document.getElementById('lbtn').disabled=true;
|
| 1469 |
+
document.getElementById('prog').classList.add('vis');
|
| 1470 |
+
document.getElementById('mstatus').textContent='LOADINGβ¦';
|
| 1471 |
+
document.getElementById('mdot').classList.remove('ready');
|
| 1472 |
+
log(`loading ${modelId}`,'w');
|
| 1473 |
+
|
| 1474 |
+
try {
|
| 1475 |
+
engine = new webllm.MLCEngine();
|
| 1476 |
+
engine.setInitProgressCallback(r=>{
|
| 1477 |
+
const p=Math.round((r.progress||0)*100);
|
| 1478 |
+
document.getElementById('pfill').style.width=p+'%';
|
| 1479 |
+
document.getElementById('ptxt').textContent=r.text||`${p}%`;
|
| 1480 |
+
});
|
| 1481 |
+
await engine.reload(modelId);
|
| 1482 |
+
|
| 1483 |
+
document.getElementById('mdot').classList.add('ready');
|
| 1484 |
+
document.getElementById('mstatus').textContent='READY';
|
| 1485 |
+
document.getElementById('pfill').style.width='100%';
|
| 1486 |
+
document.getElementById('ptxt').textContent='β ready';
|
| 1487 |
+
document.getElementById('uin').disabled=false;
|
| 1488 |
+
document.getElementById('sbtn').disabled=false;
|
| 1489 |
+
document.getElementById('uin').focus();
|
| 1490 |
+
document.getElementById('welcome').classList.add('gone');
|
| 1491 |
+
const short=modelId.replace(/-q4f\d+_\d+-MLC$/,'').replace(/-MLC$/,'');
|
| 1492 |
+
document.getElementById('ctitle').textContent=`β‘ ${short}`;
|
| 1493 |
+
lss('model',modelId); cfg.model=modelId;
|
| 1494 |
+
log(`β ${short} ready`,'g');
|
| 1495 |
+
|
| 1496 |
+
const el=createAgentBubble();
|
| 1497 |
+
renderAnswer(el, `**${short}** loaded.\n\n**Active context:**\n- Soul: ${mem.soul.split('\n').find(l=>l.startsWith('#'))||'Chronos'}\n- User memory: ${mem.user.length} chars\n- Skills: ${Object.keys(mem.skills).join(', ')||'none'}\n- Tools: web_search, scrape, summarize, remember, read_memory, forget, schedule, inject_js, network_scan, rag_index, rag_search, rag_prompt\n\n**Hotkeys:** Ctrl+\` console Β· β palette Β· Ctrl+L clear`);
|
| 1498 |
+
stats.msgs++;
|
| 1499 |
+
|
| 1500 |
+
} catch(err) {
|
| 1501 |
+
log(`β load failed: ${err.message}`,'e');
|
| 1502 |
+
sysMsg(`β Model load failed:\n${err.message}\n\nβ’ Ensure WebGPU enabled\nβ’ Reload page (COI service worker needs activation)\nβ’ Try smaller model\nβ’ Check chrome://gpu`);
|
| 1503 |
+
document.getElementById('lbtn').disabled=false;
|
| 1504 |
+
document.getElementById('mstatus').textContent='ERROR';
|
| 1505 |
+
}
|
| 1506 |
+
});
|
| 1507 |
+
|
| 1508 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1509 |
+
// INPUT HANDLING
|
| 1510 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1511 |
+
document.getElementById('sbtn').addEventListener('click',()=>{
|
| 1512 |
+
const t=document.getElementById('uin').value.trim();
|
| 1513 |
+
if(t&&engine){document.getElementById('uin').value='';runAgent(t);}
|
| 1514 |
+
});
|
| 1515 |
+
document.getElementById('uin').addEventListener('keydown',e=>{
|
| 1516 |
+
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();document.getElementById('sbtn').click();return;}
|
| 1517 |
+
if(e.key==='ArrowUp'&&!e.shiftKey){
|
| 1518 |
+
const inp=document.getElementById('uin');
|
| 1519 |
+
if(inp.value.trim()===''){e.preventDefault();openPalette();}
|
| 1520 |
+
}
|
| 1521 |
+
});
|
| 1522 |
+
document.getElementById('uin').addEventListener('input',function(){
|
| 1523 |
+
this.style.height='auto';this.style.height=Math.min(this.scrollHeight,300)+'px';
|
| 1524 |
+
});
|
| 1525 |
+
document.getElementById('stopbtn').addEventListener('click',()=>{if(abortCtrl){abortCtrl.abort();log('β stopped','w');}});
|
| 1526 |
+
|
| 1527 |
+
// Global hotkeys
|
| 1528 |
+
document.addEventListener('keydown',e=>{
|
| 1529 |
+
if(e.ctrlKey&&e.key==='l'){e.preventDefault();clearChat();}
|
| 1530 |
+
if(e.key==='`'&&(e.ctrlKey||e.metaKey)){e.preventDefault();toggleConsole();}
|
| 1531 |
+
if(e.key==='Escape'){
|
| 1532 |
+
if(paletteOpen) closePalette();
|
| 1533 |
+
else if(document.getElementById('console-popup').classList.contains('vis')) toggleConsole();
|
| 1534 |
+
else if(document.getElementById('modal').classList.contains('vis')) closeModal();
|
| 1535 |
+
}
|
| 1536 |
+
});
|
| 1537 |
+
|
| 1538 |
+
document.getElementById('brave-in').value=cfg.brave;
|
| 1539 |
+
|
| 1540 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1541 |
+
// INIT
|
| 1542 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1543 |
+
const isoP=document.getElementById('iso-p');
|
| 1544 |
+
if (window.crossOriginIsolated) {
|
| 1545 |
+
isoP.textContent='ISO: β'; isoP.style.color='var(--fg)';
|
| 1546 |
+
document.getElementById('setup-banner').classList.add('hidden');
|
| 1547 |
+
log('β Cross-origin isolated','g');
|
| 1548 |
+
} else {
|
| 1549 |
+
const isHTTP = location.protocol.startsWith('http');
|
| 1550 |
+
if (isHTTP) {
|
| 1551 |
+
isoP.textContent='ISO: β³'; isoP.style.color='var(--amber)';
|
| 1552 |
+
document.getElementById('setup-banner').classList.add('hidden');
|
| 1553 |
+
log('β ISO pending β reload if model load fails','w');
|
| 1554 |
+
} else {
|
| 1555 |
+
isoP.textContent='ISO: β'; isoP.style.color='var(--red)';
|
| 1556 |
+
log('β NOT isolated β run: python proxy.py 8080','e');
|
| 1557 |
+
setTimeout(()=>{ if(!window.crossOriginIsolated) document.getElementById('setup-banner').classList.remove('hidden'); }, 1200);
|
| 1558 |
+
}
|
| 1559 |
+
}
|
| 1560 |
+
|
| 1561 |
+
if (!navigator.gpu) log('β WebGPU not detected β Chrome 113+ required','e');
|
| 1562 |
+
else log('β WebGPU available','g');
|
| 1563 |
+
|
| 1564 |
+
refreshFilesUI();
|
| 1565 |
+
refreshMemView();
|
| 1566 |
+
refreshSchedUI();
|
| 1567 |
+
refreshRagUI();
|
| 1568 |
+
log('Chronos agent initialized','g');
|
| 1569 |
+
log(`Skills: ${Object.keys(mem.skills).join(', ')}`,'');
|
| 1570 |
+
log('Ctrl+` console Β· β palette Β· Ctrl+L clear','');
|
| 1571 |
+
|
| 1572 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1573 |
+
// RAG UI FUNCTIONS
|
| 1574 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 1575 |
+
function refreshRagUI() {
|
| 1576 |
+
const s = ragEngine.getStats();
|
| 1577 |
+
document.getElementById('rag-status').textContent = s.indexed ? 'β ready' : 'no index';
|
| 1578 |
+
document.getElementById('rag-status').style.color = s.indexed ? 'var(--fg)' : '';
|
| 1579 |
+
document.getElementById('rag-sent-count').textContent = s.sentences;
|
| 1580 |
+
document.getElementById('rag-term-count').textContent = s.uniqueTerms;
|
| 1581 |
+
// Render source chips
|
| 1582 |
+
const srcEl = document.getElementById('rag-sources');
|
| 1583 |
+
srcEl.innerHTML = ragSources.map(s => `<span class="rag-source-chip">${esc(s)}</span>`).join('');
|
| 1584 |
+
}
|
| 1585 |
+
|
| 1586 |
+
window.ragIndex = function() {
|
| 1587 |
+
const text = document.getElementById('rag-text').value.trim();
|
| 1588 |
+
if (!text) { log('RAG: no text to index','w'); return; }
|
| 1589 |
+
const result = ragEngine.addText(text);
|
| 1590 |
+
ragSources.push('manual-' + ragSources.length);
|
| 1591 |
+
document.getElementById('rag-text').value = '';
|
| 1592 |
+
refreshRagUI();
|
| 1593 |
+
log(`π RAG indexed: ${result.sentences} sent, ${result.uniqueTerms} terms`, 'g');
|
| 1594 |
+
};
|
| 1595 |
+
|
| 1596 |
+
window.ragIndexFromScrape = function() {
|
| 1597 |
+
// Index the last scraped content if available
|
| 1598 |
+
const cards = document.querySelectorAll('.tcard');
|
| 1599 |
+
let found = false;
|
| 1600 |
+
for (let i = cards.length - 1; i >= 0; i--) {
|
| 1601 |
+
const name = cards[i].querySelector('.tname');
|
| 1602 |
+
if (name && name.textContent.trim() === 'scrape') {
|
| 1603 |
+
const res = cards[i].querySelector('.tres');
|
| 1604 |
+
if (res && res.textContent.length > 20) {
|
| 1605 |
+
const result = ragEngine.addText(res.textContent);
|
| 1606 |
+
ragSources.push('scrape-result');
|
| 1607 |
+
refreshRagUI();
|
| 1608 |
+
log(`π RAG indexed scraped content: ${result.sentences} sent`, 'g');
|
| 1609 |
+
found = true;
|
| 1610 |
+
break;
|
| 1611 |
+
}
|
| 1612 |
+
}
|
| 1613 |
+
}
|
| 1614 |
+
if (!found) log('RAG: no scraped content found to index','w');
|
| 1615 |
+
};
|
| 1616 |
+
|
| 1617 |
+
window.ragIndexFromMemory = function() {
|
| 1618 |
+
if (mem.user && mem.user.length > 20) {
|
| 1619 |
+
const result = ragEngine.addText(mem.user);
|
| 1620 |
+
ragSources.push('user-memory');
|
| 1621 |
+
refreshRagUI();
|
| 1622 |
+
log(`π RAG indexed user memory: ${result.sentences} sent`, 'g');
|
| 1623 |
+
} else {
|
| 1624 |
+
log('RAG: user memory too short to index','w');
|
| 1625 |
+
}
|
| 1626 |
+
};
|
| 1627 |
+
|
| 1628 |
+
window.ragClear = function() {
|
| 1629 |
+
ragEngine.clear();
|
| 1630 |
+
ragSources = [];
|
| 1631 |
+
document.getElementById('rag-results').innerHTML = '';
|
| 1632 |
+
refreshRagUI();
|
| 1633 |
+
log('π RAG index cleared','w');
|
| 1634 |
+
};
|
| 1635 |
+
|
| 1636 |
+
window.ragSearch = function() {
|
| 1637 |
+
const query = document.getElementById('rag-query').value.trim();
|
| 1638 |
+
if (!query) { log('RAG: no query','w'); return; }
|
| 1639 |
+
if (!ragEngine.indexed) { log('RAG: nothing indexed yet','w'); return; }
|
| 1640 |
+
const result = ragEngine.query(query, 5, 1);
|
| 1641 |
+
const el = document.getElementById('rag-results');
|
| 1642 |
+
if (result.passages.length === 0) {
|
| 1643 |
+
el.innerHTML = '<div style="color:var(--fg-dim);font-style:italic">No passages found.</div>';
|
| 1644 |
+
return;
|
| 1645 |
+
}
|
| 1646 |
+
el.innerHTML = result.passages.map((p, i) =>
|
| 1647 |
+
`<div class="rag-passage"><div class="rag-score">#${i+1} score: ${p.score.toFixed(2)}</div><div class="rag-text">${esc(p.text)}</div></div>`
|
| 1648 |
+
).join('');
|
| 1649 |
+
log(`π RAG: ${result.passages.length} passages for "${query}"`, 'g');
|
| 1650 |
+
};
|
| 1651 |
+
|
| 1652 |
+
window.ragInjectPrompt = function() {
|
| 1653 |
+
const query = document.getElementById('rag-query').value.trim();
|
| 1654 |
+
if (!query) { log('RAG: no query for prompt','w'); return; }
|
| 1655 |
+
if (!ragEngine.indexed) { log('RAG: nothing indexed','w'); return; }
|
| 1656 |
+
const result = ragEngine.query(query, 5, 1);
|
| 1657 |
+
if (result.passages.length === 0) { log('RAG: no passages to inject','w'); return; }
|
| 1658 |
+
// Inject RAG context into the chat input
|
| 1659 |
+
const inp = document.getElementById('uin');
|
| 1660 |
+
inp.value = result.prompt;
|
| 1661 |
+
inp.style.height = 'auto';
|
| 1662 |
+
inp.style.height = Math.min(inp.scrollHeight, 300) + 'px';
|
| 1663 |
+
inp.focus();
|
| 1664 |
+
log(`π RAG prompt injected (${result.passages.length} passages)`, 'g');
|
| 1665 |
+
};
|
| 1666 |
+
|
| 1667 |
+
// Allow Enter in RAG query to trigger search
|
| 1668 |
+
document.getElementById('rag-query').addEventListener('keydown', function(e) {
|
| 1669 |
+
if (e.key === 'Enter') { e.preventDefault(); ragSearch(); }
|
| 1670 |
+
});
|
| 1671 |
+
|
| 1672 |
+
// Auto network scan on init
|
| 1673 |
+
toolNetworkScan().catch(()=>{});
|
| 1674 |
+
</script>
|
| 1675 |
+
</body>
|
| 1676 |
+
</html>
|
hybrid_rag_lib.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 2 |
+
// HYBRID RAG RETRIEVAL LIBRARY
|
| 3 |
+
// Pure JS Β· No dependencies Β· Browser-ready
|
| 4 |
+
// βββββββββββββββββββββββββββββββββββββββββββ
|
| 5 |
+
|
| 6 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 7 |
+
// TextProcessor β normalization, sentence splitting, tokenization
|
| 8 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 9 |
+
class TextProcessor {
|
| 10 |
+
|
| 11 |
+
static normalize(text) {
|
| 12 |
+
return text
|
| 13 |
+
.toLowerCase()
|
| 14 |
+
.replace(/[^\w\s]/g, '')
|
| 15 |
+
.split(/\s+/)
|
| 16 |
+
.filter(w => w.length > 2);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
static splitSentences(text) {
|
| 20 |
+
return text
|
| 21 |
+
.replace(/\n/g, ' ')
|
| 22 |
+
.split(/[.!?]+/)
|
| 23 |
+
.map(s => s.trim())
|
| 24 |
+
.filter(s => s.length > 0);
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 30 |
+
// Similarity β phonetic, levenshtein, n-gram
|
| 31 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
+
class Similarity {
|
| 33 |
+
|
| 34 |
+
static phonetic(word) {
|
| 35 |
+
word = word.toLowerCase();
|
| 36 |
+
return word
|
| 37 |
+
.replace(/ph/g, 'f')
|
| 38 |
+
.replace(/ee/g, 'i')
|
| 39 |
+
.replace(/ea/g, 'i')
|
| 40 |
+
.replace(/oo/g, 'u')
|
| 41 |
+
.replace(/ou/g, 'u')
|
| 42 |
+
.replace(/ck/g, 'k')
|
| 43 |
+
.replace(/c/g, 'k')
|
| 44 |
+
.replace(/z/g, 's')
|
| 45 |
+
.replace(/x/g, 'ks');
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
static levenshtein(a, b) {
|
| 49 |
+
let matrix = [];
|
| 50 |
+
for (let i = 0; i <= b.length; i++) matrix[i] = [i];
|
| 51 |
+
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
| 52 |
+
|
| 53 |
+
for (let i = 1; i <= b.length; i++) {
|
| 54 |
+
for (let j = 1; j <= a.length; j++) {
|
| 55 |
+
if (b[i - 1] === a[j - 1])
|
| 56 |
+
matrix[i][j] = matrix[i - 1][j - 1];
|
| 57 |
+
else
|
| 58 |
+
matrix[i][j] = Math.min(
|
| 59 |
+
matrix[i - 1][j - 1] + 1,
|
| 60 |
+
matrix[i][j - 1] + 1,
|
| 61 |
+
matrix[i - 1][j] + 1
|
| 62 |
+
);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
return matrix[b.length][a.length];
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
static ngrams(str, n = 3) {
|
| 69 |
+
let grams = [];
|
| 70 |
+
for (let i = 0; i <= str.length - n; i++)
|
| 71 |
+
grams.push(str.substring(i, i + n));
|
| 72 |
+
return grams;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
static ngramSimilarity(a, b) {
|
| 76 |
+
let g1 = this.ngrams(a);
|
| 77 |
+
let g2 = this.ngrams(b);
|
| 78 |
+
let set2 = new Set(g2);
|
| 79 |
+
let matches = 0;
|
| 80 |
+
g1.forEach(g => { if (set2.has(g)) matches++; });
|
| 81 |
+
return matches / Math.max(g1.length, g2.length, 1);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 87 |
+
// IndexBuilder β inverted index + document frequency
|
| 88 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 89 |
+
class IndexBuilder {
|
| 90 |
+
|
| 91 |
+
constructor() {
|
| 92 |
+
this.sentences = [];
|
| 93 |
+
this.index = {};
|
| 94 |
+
this.df = {};
|
| 95 |
+
this.docs = [];
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
build(text) {
|
| 99 |
+
this.sentences = TextProcessor.splitSentences(text);
|
| 100 |
+
this.index = {};
|
| 101 |
+
this.df = {};
|
| 102 |
+
this.docs = [];
|
| 103 |
+
|
| 104 |
+
this.sentences.forEach((sentence, id) => {
|
| 105 |
+
let words = TextProcessor.normalize(sentence);
|
| 106 |
+
this.docs[id] = words;
|
| 107 |
+
let unique = [...new Set(words)];
|
| 108 |
+
|
| 109 |
+
unique.forEach(w => {
|
| 110 |
+
if (!this.index[w]) this.index[w] = [];
|
| 111 |
+
this.index[w].push(id);
|
| 112 |
+
this.df[w] = (this.df[w] || 0) + 1;
|
| 113 |
+
});
|
| 114 |
+
});
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 120 |
+
// Ranker β BM25 + hybrid scoring
|
| 121 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 122 |
+
class Ranker {
|
| 123 |
+
|
| 124 |
+
static bm25(queryWords, words, df, N) {
|
| 125 |
+
let score = 0;
|
| 126 |
+
queryWords.forEach(q => {
|
| 127 |
+
let tf = words.filter(w => w === q).length;
|
| 128 |
+
if (tf > 0) {
|
| 129 |
+
let idf = Math.log((N + 1) / (df[q] || 1));
|
| 130 |
+
score += tf * idf * 2;
|
| 131 |
+
}
|
| 132 |
+
});
|
| 133 |
+
return score;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
static hybrid(queryWords, sentenceWords, sentence, df, N) {
|
| 137 |
+
let score = this.bm25(queryWords, sentenceWords, df, N);
|
| 138 |
+
|
| 139 |
+
queryWords.forEach(q => {
|
| 140 |
+
sentenceWords.forEach(w => {
|
| 141 |
+
let pw = Similarity.phonetic(w);
|
| 142 |
+
let pq = Similarity.phonetic(q);
|
| 143 |
+
|
| 144 |
+
if (Similarity.levenshtein(pw, pq) <= 1)
|
| 145 |
+
score += 0.7;
|
| 146 |
+
|
| 147 |
+
let sim = Similarity.ngramSimilarity(pw, pq);
|
| 148 |
+
if (sim > 0.5)
|
| 149 |
+
score += sim;
|
| 150 |
+
});
|
| 151 |
+
});
|
| 152 |
+
|
| 153 |
+
if (sentence.toLowerCase().includes(queryWords.join(' ')))
|
| 154 |
+
score += 4;
|
| 155 |
+
|
| 156 |
+
return score;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 162 |
+
// Retriever β candidate search + ranking
|
| 163 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 164 |
+
class Retriever {
|
| 165 |
+
|
| 166 |
+
constructor(indexBuilder) {
|
| 167 |
+
this.index = indexBuilder.index;
|
| 168 |
+
this.docs = indexBuilder.docs;
|
| 169 |
+
this.df = indexBuilder.df;
|
| 170 |
+
this.sentences = indexBuilder.sentences;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
search(query) {
|
| 174 |
+
let queryWords = TextProcessor.normalize(query);
|
| 175 |
+
let candidates = new Set();
|
| 176 |
+
|
| 177 |
+
queryWords.forEach(w => {
|
| 178 |
+
(this.index[w] || []).forEach(id => candidates.add(id));
|
| 179 |
+
});
|
| 180 |
+
|
| 181 |
+
// Also add fuzzy candidates via phonetic matching
|
| 182 |
+
queryWords.forEach(q => {
|
| 183 |
+
let pq = Similarity.phonetic(q);
|
| 184 |
+
Object.keys(this.index).forEach(w => {
|
| 185 |
+
let pw = Similarity.phonetic(w);
|
| 186 |
+
if (Similarity.levenshtein(pw, pq) <= 1) {
|
| 187 |
+
this.index[w].forEach(id => candidates.add(id));
|
| 188 |
+
}
|
| 189 |
+
});
|
| 190 |
+
});
|
| 191 |
+
|
| 192 |
+
let scored = [];
|
| 193 |
+
candidates.forEach(id => {
|
| 194 |
+
let words = this.docs[id];
|
| 195 |
+
let sentence = this.sentences[id];
|
| 196 |
+
let score = Ranker.hybrid(
|
| 197 |
+
queryWords,
|
| 198 |
+
words,
|
| 199 |
+
sentence,
|
| 200 |
+
this.df,
|
| 201 |
+
this.sentences.length
|
| 202 |
+
);
|
| 203 |
+
if (score > 0)
|
| 204 |
+
scored.push({ id, score, sentence });
|
| 205 |
+
});
|
| 206 |
+
|
| 207 |
+
scored.sort((a, b) => b.score - a.score);
|
| 208 |
+
return scored;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 214 |
+
// ContextBuilder β sentence window extraction
|
| 215 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 216 |
+
class ContextBuilder {
|
| 217 |
+
|
| 218 |
+
static window(sentences, id, size = 1) {
|
| 219 |
+
let start = Math.max(0, id - size);
|
| 220 |
+
let end = Math.min(sentences.length, id + size + 1);
|
| 221 |
+
return sentences.slice(start, end).join('. ');
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 227 |
+
// HybridRAG β main engine
|
| 228 |
+
// βββββββββββββββββββββββββββββββββββββββββ
|
| 229 |
+
class HybridRAG {
|
| 230 |
+
|
| 231 |
+
constructor() {
|
| 232 |
+
this.indexBuilder = new IndexBuilder();
|
| 233 |
+
this.retriever = null;
|
| 234 |
+
this.indexed = false;
|
| 235 |
+
this.sourceCount = 0;
|
| 236 |
+
this.sentenceCount = 0;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
index(text) {
|
| 240 |
+
this.indexBuilder.build(text);
|
| 241 |
+
this.retriever = new Retriever(this.indexBuilder);
|
| 242 |
+
this.indexed = true;
|
| 243 |
+
this.sourceCount++;
|
| 244 |
+
this.sentenceCount = this.indexBuilder.sentences.length;
|
| 245 |
+
return {
|
| 246 |
+
sentences: this.sentenceCount,
|
| 247 |
+
uniqueTerms: Object.keys(this.indexBuilder.index).length
|
| 248 |
+
};
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
addText(text) {
|
| 252 |
+
// Append to existing index by rebuilding with combined text
|
| 253 |
+
const existingSentences = this.indexBuilder.sentences.join('. ');
|
| 254 |
+
const combined = existingSentences ? existingSentences + '. ' + text : text;
|
| 255 |
+
return this.index(combined);
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
query(query, topK = 5, windowSize = 1) {
|
| 259 |
+
if (!this.indexed || !this.retriever) {
|
| 260 |
+
return { passages: [], prompt: '', ranked: [], error: 'No text indexed yet.' };
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
let ranked = this.retriever.search(query);
|
| 264 |
+
let passages = [];
|
| 265 |
+
let seen = new Set();
|
| 266 |
+
|
| 267 |
+
ranked.slice(0, topK).forEach(r => {
|
| 268 |
+
let ctx = ContextBuilder.window(
|
| 269 |
+
this.indexBuilder.sentences,
|
| 270 |
+
r.id,
|
| 271 |
+
windowSize
|
| 272 |
+
);
|
| 273 |
+
// Deduplicate overlapping windows
|
| 274 |
+
if (!seen.has(ctx)) {
|
| 275 |
+
seen.add(ctx);
|
| 276 |
+
passages.push({
|
| 277 |
+
text: ctx,
|
| 278 |
+
score: r.score,
|
| 279 |
+
sentenceId: r.id,
|
| 280 |
+
original: r.sentence
|
| 281 |
+
});
|
| 282 |
+
}
|
| 283 |
+
});
|
| 284 |
+
|
| 285 |
+
let prompt =
|
| 286 |
+
'Use the following context to answer the question:\n\n' +
|
| 287 |
+
passages.map((p, i) => `[${i + 1}] (score: ${p.score.toFixed(2)}) ${p.text}`).join('\n\n') +
|
| 288 |
+
'\n\nQuestion: ' + query +
|
| 289 |
+
'\nAnswer:';
|
| 290 |
+
|
| 291 |
+
return {
|
| 292 |
+
passages,
|
| 293 |
+
prompt,
|
| 294 |
+
ranked: ranked.slice(0, topK),
|
| 295 |
+
totalCandidates: ranked.length
|
| 296 |
+
};
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
getStats() {
|
| 300 |
+
return {
|
| 301 |
+
indexed: this.indexed,
|
| 302 |
+
sentences: this.sentenceCount,
|
| 303 |
+
uniqueTerms: Object.keys(this.indexBuilder.index).length,
|
| 304 |
+
sources: this.sourceCount
|
| 305 |
+
};
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
clear() {
|
| 309 |
+
this.indexBuilder = new IndexBuilder();
|
| 310 |
+
this.retriever = null;
|
| 311 |
+
this.indexed = false;
|
| 312 |
+
this.sourceCount = 0;
|
| 313 |
+
this.sentenceCount = 0;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
}
|