Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- app.py +91 -0
- templates/index.html +243 -0
app.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
from flask import Flask, render_template, request, jsonify, Response, stream_with_context
|
| 3 |
+
from visumem_core import SectorMemory
|
| 4 |
+
import ai_engine
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
app = Flask(__name__)
|
| 8 |
+
|
| 9 |
+
mem_history = SectorMemory("history")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@app.route('/')
|
| 13 |
+
def index():
|
| 14 |
+
return render_template('index.html')
|
| 15 |
+
|
| 16 |
+
@app.route('/visualize/<mem_type>')
|
| 17 |
+
def visualize(mem_type):
|
| 18 |
+
target = mem_history
|
| 19 |
+
|
| 20 |
+
# 1. Generate Image
|
| 21 |
+
img_bytes = target.to_image_bytes()
|
| 22 |
+
b64 = base64.b64encode(img_bytes).decode('utf-8')
|
| 23 |
+
|
| 24 |
+
# 2. Get Stats (Updated for RGB Core Compatibility)
|
| 25 |
+
# The RGB core uses .count for items and .cursor_green for text size
|
| 26 |
+
# It does not use .slot_cursor or .heap_cursor anymore.
|
| 27 |
+
active_count = target.count
|
| 28 |
+
text_usage = target.cursor_green
|
| 29 |
+
|
| 30 |
+
return jsonify({
|
| 31 |
+
"image": f"data:image/png;base64,{b64}",
|
| 32 |
+
"stats": f"{active_count} Active Entries",
|
| 33 |
+
"usage": f"{text_usage} Bytes Text"
|
| 34 |
+
})
|
| 35 |
+
|
| 36 |
+
@app.route('/inspect/<mem_type>')
|
| 37 |
+
def inspect(mem_type):
|
| 38 |
+
content = mem_history.dump_heap_content()
|
| 39 |
+
return jsonify({"content": content})
|
| 40 |
+
|
| 41 |
+
@app.route('/chat', methods=['POST'])
|
| 42 |
+
def chat():
|
| 43 |
+
user_msg = request.json.get('message')
|
| 44 |
+
if not user_msg: return jsonify({"error": "Empty"}), 400
|
| 45 |
+
|
| 46 |
+
def generate():
|
| 47 |
+
# 1. VECTORIZE USER INPUT
|
| 48 |
+
q_vec = ai_engine.get_embedding(user_msg)
|
| 49 |
+
|
| 50 |
+
# B. Relevant Past History
|
| 51 |
+
hist_hits = mem_history.search(q_vec, top_k=2)
|
| 52 |
+
long_term_txt = "\n".join([f"[Memory]: {h['text']}" for h in hist_hits if h['score'] > 0.4])
|
| 53 |
+
|
| 54 |
+
# C. Recent Conversation
|
| 55 |
+
recent_msgs = mem_history.get_recent_entries(n=4)
|
| 56 |
+
recent_txt = "\n".join(recent_msgs)
|
| 57 |
+
|
| 58 |
+
# 3. BUILD PROMPT
|
| 59 |
+
system_prompt = f"""You are a helpful AI Assistant with an evolving rule system and knowledge base.
|
| 60 |
+
|
| 61 |
+
### RELEVANT MEMORIES (Context from past):
|
| 62 |
+
{long_term_txt}
|
| 63 |
+
|
| 64 |
+
### CURRENT CONVERSATION:
|
| 65 |
+
{recent_txt}
|
| 66 |
+
"""
|
| 67 |
+
|
| 68 |
+
messages = [
|
| 69 |
+
{"role": "system", "content": system_prompt},
|
| 70 |
+
{"role": "user", "content": user_msg}
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
# 4. STREAM GENERATION
|
| 74 |
+
full_response = ""
|
| 75 |
+
for chunk in ai_engine.chat_stream(messages):
|
| 76 |
+
full_response += chunk
|
| 77 |
+
yield chunk
|
| 78 |
+
|
| 79 |
+
# 5. BACKGROUND: WRITE & REFLECT
|
| 80 |
+
log_entry = f"User: {user_msg}\nAI: {full_response}"
|
| 81 |
+
mem_history.write_entry(log_entry, q_vec)
|
| 82 |
+
|
| 83 |
+
return Response(stream_with_context(generate()), mimetype='text/plain')
|
| 84 |
+
|
| 85 |
+
@app.route('/wipe', methods=['POST'])
|
| 86 |
+
def wipe():
|
| 87 |
+
mem_history.wipe()
|
| 88 |
+
return jsonify({"success":True})
|
| 89 |
+
|
| 90 |
+
if __name__ == '__main__':
|
| 91 |
+
app.run(debug=True, port=5000)
|
templates/index.html
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>VisuMem AI // Recursive Visual Memory</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 7 |
+
|
| 8 |
+
<!-- Markdown & Syntax Highlighting -->
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 10 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css">
|
| 11 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
| 12 |
+
|
| 13 |
+
<style>
|
| 14 |
+
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap');
|
| 15 |
+
body { background-color: #050505; color: #a29bfe; font-family: 'Space Mono', monospace; }
|
| 16 |
+
|
| 17 |
+
.pixelated {
|
| 18 |
+
image-rendering: pixelated;
|
| 19 |
+
width: 100%;
|
| 20 |
+
border: 1px solid #6c5ce7;
|
| 21 |
+
box-shadow: 0 0 15px rgba(108, 92, 231, 0.2);
|
| 22 |
+
background: #000;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.panel { background: #111; border: 1px solid #333; padding: 15px; border-radius: 4px; }
|
| 26 |
+
.btn { background: #1a1a1a; border: 1px solid #6c5ce7; color: #6c5ce7; padding: 6px 12px; font-size: 10px; transition: 0.2s; text-transform: uppercase; }
|
| 27 |
+
.btn:hover { background: #6c5ce7; color: #000; cursor: pointer; }
|
| 28 |
+
|
| 29 |
+
/* Chat Styling */
|
| 30 |
+
.chat-bubble { padding: 10px; margin-bottom: 12px; font-size: 12px; border-left: 2px solid #6c5ce7; background: #0a0a0a; overflow-wrap: break-word;}
|
| 31 |
+
.system-msg { color: #fab1a0; font-style: italic; display: block; margin-top: 5px; border-top: 1px dashed #333; padding-top: 5px; }
|
| 32 |
+
|
| 33 |
+
/* Markdown Overrides */
|
| 34 |
+
.prose p { margin-bottom: 0.5em; }
|
| 35 |
+
.prose pre {
|
| 36 |
+
background: #1e1e1e !important;
|
| 37 |
+
padding: 10px;
|
| 38 |
+
border-radius: 4px;
|
| 39 |
+
overflow-x: auto;
|
| 40 |
+
border: 1px solid #333;
|
| 41 |
+
margin: 10px 0;
|
| 42 |
+
}
|
| 43 |
+
.prose code {
|
| 44 |
+
font-family: 'Courier New', monospace;
|
| 45 |
+
font-size: 11px;
|
| 46 |
+
color: #e0e0e0;
|
| 47 |
+
}
|
| 48 |
+
.prose ul { list-style-type: disc; margin-left: 20px; }
|
| 49 |
+
.prose ol { list-style-type: decimal; margin-left: 20px; }
|
| 50 |
+
.prose strong { color: #fff; }
|
| 51 |
+
.prose a { color: #6c5ce7; text-decoration: underline; }
|
| 52 |
+
|
| 53 |
+
/* Modal for Inspection */
|
| 54 |
+
#inspector { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; height: 80%; background: #000; border: 2px solid #6c5ce7; z-index: 50; display: none; padding: 20px; overflow: auto; }
|
| 55 |
+
.close-btn { position: absolute; top: 10px; right: 10px; color: red; cursor: pointer; }
|
| 56 |
+
</style>
|
| 57 |
+
</head>
|
| 58 |
+
<body class="p-4 md:p-8 max-w-7xl mx-auto">
|
| 59 |
+
|
| 60 |
+
<!-- Header -->
|
| 61 |
+
<div class="flex justify-between items-end mb-6 border-b border-[#333] pb-4">
|
| 62 |
+
<div>
|
| 63 |
+
<h1 class="text-2xl font-bold text-white tracking-widest">VISUMEM_AI <span class="text-xs text-[#6c5ce7]">v2.2</span></h1>
|
| 64 |
+
<p class="text-gray-500 text-[10px]">RECURSIVE VISUAL STORAGE // MARKDOWN ENABLED</p>
|
| 65 |
+
</div>
|
| 66 |
+
<div class="text-right">
|
| 67 |
+
<div class="text-[10px] text-green-500">ENGINE: ACTIVE</div>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
|
| 71 |
+
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
| 72 |
+
|
| 73 |
+
<!-- VISUALIZERS -->
|
| 74 |
+
<div class="lg:col-span-4 flex flex-col gap-6">
|
| 75 |
+
|
| 76 |
+
<!-- History -->
|
| 77 |
+
<div class="panel">
|
| 78 |
+
<div class="flex justify-between items-center mb-2">
|
| 79 |
+
<h2 class="text-xs font-bold text-white">EPISODIC LOG</h2>
|
| 80 |
+
<button onclick="inspect('history')" class="btn">DECODE</button>
|
| 81 |
+
</div>
|
| 82 |
+
<div class="relative w-full aspect-square mb-2">
|
| 83 |
+
<img id="img-history" src="" class="pixelated">
|
| 84 |
+
</div>
|
| 85 |
+
<div class="text-[10px] text-gray-500 flex justify-between">
|
| 86 |
+
<span id="stat-history">--</span>
|
| 87 |
+
<span id="usage-history">--</span>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<button onclick="wipe()" class="btn border-red-900 text-red-500 hover:bg-red-900 hover:text-white">
|
| 92 |
+
HARD RESET (WIPE ALL)
|
| 93 |
+
</button>
|
| 94 |
+
</div>
|
| 95 |
+
|
| 96 |
+
<!-- CHAT -->
|
| 97 |
+
<div class="lg:col-span-8 flex flex-col h-[85vh]">
|
| 98 |
+
<div id="chat-log" class="panel flex-1 overflow-y-auto mb-4 font-mono text-gray-300">
|
| 99 |
+
<div class="text-center text-gray-700 text-xs mt-20">
|
| 100 |
+
// SYSTEM READY. WAITING FOR INPUT.
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<form id="chat-form" class="flex gap-2">
|
| 105 |
+
<input type="text" id="user-input" class="bg-[#111] border border-[#333] text-white p-3 flex-1 outline-none focus:border-[#6c5ce7]" placeholder="Type your message..." autocomplete="off">
|
| 106 |
+
<button type="submit" class="btn px-6 font-bold text-white bg-[#6c5ce7] text-black hover:bg-white">SEND</button>
|
| 107 |
+
</form>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<!-- Inspector Modal -->
|
| 112 |
+
<div id="inspector">
|
| 113 |
+
<div class="close-btn" onclick="closeInspect()">[X] CLOSE</div>
|
| 114 |
+
<h2 class="text-xl font-bold mb-4 text-white" id="inspect-title">MEMORY DUMP</h2>
|
| 115 |
+
<div id="inspect-content" class="font-mono text-xs text-green-400 whitespace-pre-wrap"></div>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<script>
|
| 119 |
+
const chatLog = document.getElementById('chat-log');
|
| 120 |
+
const userInput = document.getElementById('user-input');
|
| 121 |
+
|
| 122 |
+
// Configure Marked.js with Highlight.js
|
| 123 |
+
marked.setOptions({
|
| 124 |
+
highlight: function(code, lang) {
|
| 125 |
+
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
| 126 |
+
return hljs.highlight(code, { language }).value;
|
| 127 |
+
},
|
| 128 |
+
langPrefix: 'hljs language-',
|
| 129 |
+
breaks: true,
|
| 130 |
+
gfm: true
|
| 131 |
+
});
|
| 132 |
+
|
| 133 |
+
setInterval(refreshVisuals, 2000);
|
| 134 |
+
|
| 135 |
+
async function refreshVisuals() {
|
| 136 |
+
try {
|
| 137 |
+
|
| 138 |
+
const r2 = await fetch('/visualize/history');
|
| 139 |
+
const d2 = await r2.json();
|
| 140 |
+
document.getElementById('img-history').src = d2.image;
|
| 141 |
+
document.getElementById('stat-history').textContent = d2.stats;
|
| 142 |
+
document.getElementById('usage-history').textContent = d2.usage;
|
| 143 |
+
} catch(e) {}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
async function inspect(type) {
|
| 147 |
+
document.getElementById('inspector').style.display = 'block';
|
| 148 |
+
document.getElementById('inspect-title').innerText = `DECODING ${type.toUpperCase()} SECTOR...`;
|
| 149 |
+
document.getElementById('inspect-content').innerText = "Reading bytes...";
|
| 150 |
+
|
| 151 |
+
const res = await fetch(`/inspect/${type}`);
|
| 152 |
+
const data = await res.json();
|
| 153 |
+
|
| 154 |
+
document.getElementById('inspect-content').innerText = data.content.join('\n\n');
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
function closeInspect() {
|
| 158 |
+
document.getElementById('inspector').style.display = 'none';
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
document.getElementById('chat-form').addEventListener('submit', async (e) => {
|
| 162 |
+
e.preventDefault();
|
| 163 |
+
const text = userInput.value.trim();
|
| 164 |
+
if(!text) return;
|
| 165 |
+
|
| 166 |
+
// Add User Message (No MD for user to keep it raw/clean)
|
| 167 |
+
addMsg("USER", text);
|
| 168 |
+
userInput.value = "";
|
| 169 |
+
|
| 170 |
+
// Create AI Message Container
|
| 171 |
+
const aiDiv = addMsg("AI", "...");
|
| 172 |
+
const aiContentDiv = document.createElement("div");
|
| 173 |
+
aiContentDiv.className = "prose"; // Tailwind typography class hook
|
| 174 |
+
aiDiv.innerHTML = "<strong>AI:</strong> ";
|
| 175 |
+
aiDiv.appendChild(aiContentDiv);
|
| 176 |
+
|
| 177 |
+
const res = await fetch('/chat', {
|
| 178 |
+
method: 'POST',
|
| 179 |
+
headers: {'Content-Type': 'application/json'},
|
| 180 |
+
body: JSON.stringify({message: text})
|
| 181 |
+
});
|
| 182 |
+
|
| 183 |
+
const reader = res.body.getReader();
|
| 184 |
+
const decoder = new TextDecoder();
|
| 185 |
+
|
| 186 |
+
let rawBuffer = "";
|
| 187 |
+
|
| 188 |
+
while(true) {
|
| 189 |
+
const { done, value } = await reader.read();
|
| 190 |
+
if (done) break;
|
| 191 |
+
const chunk = decoder.decode(value);
|
| 192 |
+
rawBuffer += chunk;
|
| 193 |
+
|
| 194 |
+
// 1. Separate actual content from [SYSTEM: ...] logs
|
| 195 |
+
// We use Regex to isolate the system logs so Markdown doesn't mess them up
|
| 196 |
+
// and so we can style them differently.
|
| 197 |
+
|
| 198 |
+
// Temporary placeholder for rendering
|
| 199 |
+
let renderText = rawBuffer;
|
| 200 |
+
|
| 201 |
+
// Regex to find system messages
|
| 202 |
+
const sysRegex = /\[SYSTEM:.*?\]/g;
|
| 203 |
+
|
| 204 |
+
// We let marked parse everything, but we pre-format the system tags slightly
|
| 205 |
+
// to ensure they survive properly or post-process.
|
| 206 |
+
// Simpler approach: Render Markdown, then replace the text matching system logs with styled HTML.
|
| 207 |
+
|
| 208 |
+
let html = marked.parse(renderText);
|
| 209 |
+
|
| 210 |
+
// Highlight System Logs after markdown conversion
|
| 211 |
+
html = html.replace(sysRegex, (match) => {
|
| 212 |
+
return `<span class="system-msg">${match}</span>`;
|
| 213 |
+
});
|
| 214 |
+
|
| 215 |
+
aiContentDiv.innerHTML = html;
|
| 216 |
+
chatLog.scrollTop = chatLog.scrollHeight;
|
| 217 |
+
}
|
| 218 |
+
refreshVisuals();
|
| 219 |
+
});
|
| 220 |
+
|
| 221 |
+
function addMsg(role, text) {
|
| 222 |
+
const div = document.createElement('div');
|
| 223 |
+
div.className = "chat-bubble";
|
| 224 |
+
if(role === "USER") {
|
| 225 |
+
div.innerHTML = `<strong>${role}:</strong> ${text}`;
|
| 226 |
+
} else {
|
| 227 |
+
div.innerHTML = `<strong>${role}:</strong> ${text}`;
|
| 228 |
+
}
|
| 229 |
+
chatLog.appendChild(div);
|
| 230 |
+
chatLog.scrollTop = chatLog.scrollHeight;
|
| 231 |
+
return div;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
async function wipe() {
|
| 235 |
+
if(confirm("Really wipe memory?")) {
|
| 236 |
+
await fetch('/wipe', {method:'POST'});
|
| 237 |
+
refreshVisuals();
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
refreshVisuals();
|
| 241 |
+
</script>
|
| 242 |
+
</body>
|
| 243 |
+
</html>
|