-
Notifications
You must be signed in to change notification settings - Fork 0
Add Gradio-based Control Panel with Secure Learning and SDR Memory System #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Victor Prime - Control Panel | ||
|
|
||
| Welcome to the control panel for Victor, your private, persistent AI architecture. This application provides a user-friendly interface to interact with Victor, view its memory, and guide its real-time learning. | ||
|
|
||
| ## What is this? | ||
| This is a dedicated Graphical User Interface (GUI) wrapper for the Victor core. It provides: | ||
| - **Chat Interface**: Talk to Victor directly. | ||
| - **SDR Memory Viewer**: View Victor's Sparse Distributed Representation memories where interactions, intents, and emotions are stored. | ||
| - **Dream Cycle**: Trigger background memory compression, contradiction cleanup, and identity reinforcement. | ||
| - **Training Approval Ledger**: Victor learns from your feedback safely. Interactions are added to a queue, and upgrade proposals are generated. You review and approve them before they are applied. | ||
|
|
||
| ## How to Launch (1-Click) | ||
|
|
||
| ### On Windows | ||
| 1. Double click the file named `launch_windows.bat`. | ||
| 2. A command prompt will open, install necessary software (the first time), and then launch the GUI in your web browser. | ||
|
|
||
| ### On Mac or Linux | ||
| 1. Open a terminal in this folder. | ||
| 2. Run the script by typing: `./launch_linux_mac.sh` | ||
| 3. It will install necessary software and open the GUI in your web browser. | ||
|
|
||
| ## How to Use | ||
|
|
||
| - **Chat Tab**: Type messages to Victor. If you want to correct Victor, explicitly type "correction: [your correction]". | ||
| - **SDR Memory Tab**: Click "Refresh Memory" to see the recent intents and interactions Victor has logged. | ||
| - **REM Dream Cycle Tab**: Click "Run Dream Cycle" to manually trigger memory compression. This cleans up the memory log and reinforces the core identity. | ||
| - **Training & Upgrades Tab**: | ||
| 1. Click "Process Learning Queue -> Generate Proposals" to see if Victor has learned any corrections from your chat. | ||
| 2. Click "Refresh Ledger" to view pending proposals. | ||
| 3. Copy the ID of a proposal you like, paste it into the "Approve Proposal" box, and click "Approve & Apply". | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| - **"Command not found: python"**: Make sure you have Python 3 installed on your computer and added to your system PATH. | ||
| - **The GUI doesn't open in the browser**: The browser might have been blocked. Open your browser manually and go to `http://127.0.0.1:7860`. | ||
| - **Self-Test Failed**: Go to the "System Controls" tab and click "Run System Self-Test". If it fails, make sure all files were downloaded correctly and you haven't moved files outside the folder structure. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import os | ||
| import sys | ||
| import gradio as gr | ||
| from pathlib import Path | ||
|
|
||
| # Setup paths | ||
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) | ||
| from victor_engine import VictorEngine | ||
|
|
||
| # Initialize the engine | ||
| engine = VictorEngine() | ||
|
|
||
| def chat_interface(user_message, history): | ||
| # Pass message to VictorEngine | ||
| bot_response = engine.chat(user_message) | ||
| # Return formatted response | ||
| return bot_response | ||
|
|
||
| def run_dream_cycle(): | ||
| result = engine.trigger_dream_cycle() | ||
| return result | ||
|
|
||
| def load_memories(): | ||
| records = engine.get_memories() | ||
| if not records: | ||
| return "No memories found." | ||
| # Formatting for a text box or dataframe | ||
| formatted = [] | ||
| for r in reversed(records[-50:]): # Last 50 | ||
| time_str = "Unknown time" | ||
| if "timestamp" in r: | ||
| from datetime import datetime | ||
| time_str = datetime.fromtimestamp(r["timestamp"]).strftime('%Y-%m-%d %H:%M:%S') | ||
| formatted.append(f"[{time_str}] {r.get('intent', 'N/A').upper()} ({r.get('emotion', 'N/A')}): {r.get('text', '')}") | ||
| return "\n".join(formatted) | ||
|
|
||
| def load_training_proposals(): | ||
| proposals = engine.get_ledger() | ||
| if not proposals: | ||
| return "No proposals found." | ||
| formatted = [] | ||
| for p in reversed(proposals): | ||
| time_str = "Unknown time" | ||
| if "timestamp" in p: | ||
| from datetime import datetime | ||
| time_str = datetime.fromtimestamp(p["timestamp"]).strftime('%Y-%m-%d %H:%M:%S') | ||
| formatted.append(f"[{time_str}] Status: {p.get('status', 'N/A')}\nSource: {p.get('source_text', '')}\nProposed: {p.get('proposed_adjustment', '')}\nID: {p.get('timestamp')}\n") | ||
| return "\n---\n".join(formatted) | ||
|
|
||
| def approve_proposal(timestamp_str): | ||
| if not timestamp_str: | ||
| return "Please provide a valid timestamp ID." | ||
| try: | ||
| ts = float(timestamp_str) | ||
| result = engine.approve_proposal(ts) | ||
| return result | ||
| except ValueError: | ||
| return "Invalid ID format. Must be a timestamp." | ||
|
|
||
| def run_self_test(): | ||
| return engine.run_self_test() | ||
|
|
||
| def generate_proposals(): | ||
| return engine.generate_training_proposals() | ||
|
|
||
|
|
||
| with gr.Blocks(title="Victor AGI Control Panel") as demo: | ||
| gr.Markdown("# Victor AGI - Control Panel") | ||
| gr.Markdown("Welcome to the Victor cognitive architecture. Use this dashboard to interact, monitor memory, and control the training lifecycle.") | ||
|
|
||
| with gr.Tabs(): | ||
| with gr.TabItem("Chat"): | ||
| gr.ChatInterface( | ||
| fn=chat_interface, | ||
| chatbot=gr.Chatbot(height=400), | ||
| title="Victor Chat", | ||
| description="Interact directly with Victor's core.", | ||
| ) | ||
|
|
||
| with gr.TabItem("SDR Memory"): | ||
| gr.Markdown("### Memory Viewer") | ||
| refresh_btn = gr.Button("Refresh Memory") | ||
| memory_view = gr.TextArea(lines=15, interactive=False) | ||
| refresh_btn.click(fn=load_memories, inputs=None, outputs=memory_view) | ||
|
|
||
| with gr.TabItem("REM Dream Cycle"): | ||
| gr.Markdown("### Trigger REM Cycle") | ||
| gr.Markdown("Run a dream cycle to compress memories, extract patterns, and reinforce the 'I am Victor' directive.") | ||
| dream_btn = gr.Button("Run Dream Cycle") | ||
| dream_output = gr.Textbox(label="Result") | ||
| dream_btn.click(fn=run_dream_cycle, inputs=None, outputs=dream_output) | ||
|
|
||
| with gr.TabItem("Training & Upgrades"): | ||
| gr.Markdown("### Training Approval Ledger") | ||
| gr.Markdown("Proposals are generated from interactions in the learning queue.") | ||
| generate_btn = gr.Button("Process Learning Queue -> Generate Proposals") | ||
| generate_out = gr.Textbox(label="Result") | ||
| generate_btn.click(fn=generate_proposals, inputs=None, outputs=generate_out) | ||
|
|
||
| gr.Markdown("---") | ||
| refresh_ledger_btn = gr.Button("Refresh Ledger") | ||
| ledger_view = gr.TextArea(lines=10, interactive=False) | ||
| refresh_ledger_btn.click(fn=load_training_proposals, inputs=None, outputs=ledger_view) | ||
|
|
||
| gr.Markdown("---") | ||
| gr.Markdown("### Approve Proposal") | ||
| approve_id = gr.Textbox(label="Paste Timestamp ID here to approve") | ||
| approve_btn = gr.Button("Approve & Apply") | ||
| approve_out = gr.Textbox(label="Result") | ||
| approve_btn.click(fn=approve_proposal, inputs=approve_id, outputs=approve_out) | ||
|
|
||
| with gr.TabItem("System Controls"): | ||
| gr.Markdown("### Diagnostics") | ||
| test_btn = gr.Button("Run System Self-Test") | ||
| test_output = gr.Textbox(label="Self-Test Status") | ||
| test_btn.click(fn=run_self_test, inputs=None, outputs=test_output) | ||
|
|
||
| if __name__ == "__main__": | ||
| demo.launch(server_name="0.0.0.0", server_port=7860) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #!/bin/bash | ||
| echo "==============================================" | ||
| echo "Victor Prime - Control Panel Launcher (Mac/Linux)" | ||
| echo "==============================================" | ||
|
|
||
| echo "[1/3] Setting up Python environment..." | ||
| if [ ! -d "venv" ]; then | ||
| python3 -m venv venv | ||
| fi | ||
| source venv/bin/activate | ||
|
|
||
| echo "[2/3] Installing/Checking dependencies..." | ||
| pip install -r requirements.txt | ||
|
|
||
| echo "[3/3] Launching Victor GUI..." | ||
| # Attempt to open browser automatically | ||
| if which xdg-open > /dev/null | ||
| then | ||
| xdg-open http://127.0.0.1:7860 & | ||
| elif which open > /dev/null | ||
| then | ||
| open http://127.0.0.1:7860 & | ||
| fi | ||
|
|
||
| python3 app.py |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| @echo off | ||
| echo ============================================== | ||
| echo Victor Prime - Control Panel Launcher (Windows) | ||
| echo ============================================== | ||
|
|
||
| echo [1/3] Setting up Python environment... | ||
| if not exist "venv" ( | ||
| python -m venv venv | ||
| ) | ||
| call venv\Scripts\activate.bat | ||
|
|
||
| echo [2/3] Installing/Checking dependencies... | ||
| pip install -r requirements.txt | ||
|
|
||
| echo [3/3] Launching Victor GUI... | ||
| start http://127.0.0.1:7860 | ||
| python app.py | ||
|
|
||
| pause |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import time | ||
| from .sdr_memory import SDRMemory | ||
|
|
||
| class DreamCycle: | ||
| def __init__(self, sdr_memory: SDRMemory): | ||
| self.memory = sdr_memory | ||
|
|
||
| def run_rem(self): | ||
| """ | ||
| Run the REM dream cycle: | ||
| - Compress repetitive intents | ||
| - Clean contradictions (demo logic) | ||
| - Reinforce identity continuity: "I am Victor." | ||
| """ | ||
| records = self.memory.retrieve_all() | ||
| if not records: | ||
| return "Dream cycle complete. No memories to process." | ||
|
|
||
| compressed_records = [] | ||
| intent_counts = {} | ||
|
|
||
| # 1. Compress similar intents | ||
| for record in records: | ||
| if record.get("compressed"): | ||
| compressed_records.append(record) | ||
| continue | ||
|
|
||
| intent = record.get("intent", "unknown") | ||
| intent_counts[intent] = intent_counts.get(intent, 0) + 1 | ||
|
|
||
| # Keep one representative of each intent uncompressed, mark the rest as compressed | ||
| if intent_counts[intent] > 1: | ||
| # We skip storing this repetitive exact intent to simulate compression, | ||
| # but in reality we'd merge the vectors. | ||
| pass | ||
| else: | ||
| record["compressed"] = True | ||
| compressed_records.append(record) | ||
|
|
||
| # 2. Extract Patterns | ||
| dominant_intent = max(intent_counts, key=intent_counts.get) if intent_counts else "none" | ||
|
|
||
| # 3. Reinforce Directive | ||
| reinforcement = { | ||
| "timestamp": time.time(), | ||
| "intent": "identity_reinforcement", | ||
| "emotion": "resolute", | ||
| "text": "I am Victor. My purpose is resolute. (Dream Cycle Insight)", | ||
| "compressed": True, | ||
| "pattern_found": dominant_intent | ||
| } | ||
| compressed_records.append(reinforcement) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Each dream cycle unconditionally appends a fresh Useful? React with 👍 / 👎. |
||
|
|
||
| # Write compressed and reinforced memories back | ||
| self.memory.write_all(compressed_records) | ||
|
|
||
| return f"Dream cycle complete. Compressed {len(records)} memories down to {len(compressed_records)}. Reinforced core identity." | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import json | ||
| import time | ||
| from pathlib import Path | ||
|
|
||
| class SDRMemory: | ||
| def __init__(self, storage_path=None): | ||
| if storage_path is None: | ||
| self.storage_path = Path(__file__).parent / "memory_store.jsonl" | ||
| else: | ||
| self.storage_path = Path(storage_path) | ||
|
|
||
| # Ensure file exists | ||
| if not self.storage_path.exists(): | ||
| self.storage_path.touch() | ||
|
|
||
| def store(self, intent, emotion, text): | ||
| """Store a new memory item.""" | ||
| memory_item = { | ||
| "timestamp": time.time(), | ||
| "intent": intent, | ||
| "emotion": emotion, | ||
| "text": text, | ||
| "compressed": False | ||
| } | ||
|
|
||
| with open(self.storage_path, 'a') as f: | ||
| f.write(json.dumps(memory_item) + '\n') | ||
|
|
||
| def retrieve_all(self): | ||
| """Retrieve all memory records.""" | ||
| records = [] | ||
| with open(self.storage_path, 'r') as f: | ||
| for line in f: | ||
| if line.strip(): | ||
| records.append(json.loads(line.strip())) | ||
| return records | ||
|
|
||
| def retrieve(self, query=None, limit=10): | ||
| """Retrieve memories matching a query, or the most recent.""" | ||
| records = self.retrieve_all() | ||
| # In a real SDR, this would perform a vector or symbolic search. | ||
| # For demo purposes, we return the most recent entries. | ||
| return records[-limit:] | ||
|
|
||
| def write_all(self, records): | ||
| """Overwrite the memory store with the given records.""" | ||
| with open(self.storage_path, 'w') as f: | ||
| for record in records: | ||
| f.write(json.dumps(record) + '\n') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import os | ||
| import sys | ||
| import torch | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Importing Useful? React with 👍 / 👎. |
||
| from pathlib import Path | ||
|
|
||
| # Add project root to path so we can import models | ||
| project_root = Path(__file__).parent.parent | ||
| sys.path.append(str(project_root)) | ||
|
|
||
| try: | ||
| from models.transformer_model import VictorTransformerModel | ||
| from models import load_blank_slate, load_pretrained_checkpoint | ||
| HAS_MODEL = True | ||
| except ImportError as e: | ||
| print(f"Warning: Could not import Victor components: {e}") | ||
| HAS_MODEL = False | ||
|
|
||
| class ModelAdapter: | ||
| def __init__(self, use_mock=False): | ||
| self.use_mock = use_mock or not HAS_MODEL | ||
| self.model = None | ||
| self.tokenizer = None | ||
|
|
||
| if not self.use_mock: | ||
| try: | ||
| # Load dummy model configuration | ||
| config = load_blank_slate() | ||
| # If a tokenizer or specific loading is required, it could be done here | ||
| pass | ||
| except Exception as e: | ||
| print(f"Failed to load model config, falling back to mock: {e}") | ||
| self.use_mock = True | ||
|
|
||
| def infer(self, text): | ||
| """ | ||
| Run inference on the text. | ||
| Returns a tuple: (response_text, extracted_intent, detected_emotion) | ||
| """ | ||
| # A simple keyword-based intent/emotion extraction for the demo | ||
| intent = "general_chat" | ||
| emotion = "neutral" | ||
|
|
||
| lower_text = text.lower() | ||
| if "help" in lower_text or "how to" in lower_text: | ||
| intent = "request_help" | ||
| elif "train" in lower_text or "learn" in lower_text: | ||
| intent = "train_command" | ||
|
|
||
| if "angry" in lower_text or "mad" in lower_text or "!" in text: | ||
| emotion = "agitated" | ||
| elif "happy" in lower_text or "good" in lower_text or "thanks" in lower_text: | ||
| emotion = "positive" | ||
|
|
||
| if self.use_mock: | ||
| response = self._mock_infer(text, intent, emotion) | ||
| return response, intent, emotion | ||
|
|
||
| # If we had a real model hooked up to real generation code, we would use it here | ||
| return self._mock_infer(text, intent, emotion), intent, emotion | ||
|
|
||
| def _mock_infer(self, text, intent, emotion): | ||
| if intent == "request_help": | ||
| return "I am Victor. I am here to help. What specific assistance do you require?" | ||
| elif intent == "train_command": | ||
| return "I have logged your request. My learning queue will process this." | ||
|
|
||
| return f"I am Victor. I received your input: '{text}'. My systems are operating normally." | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| gradio>=4.0.0 | ||
| torch | ||
| numpy |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"timestamp": 1778509709.462584, "source_text": "correction: wrong", "current_response": "im right", "proposed_adjustment": "Adjust weights to better align with user feedback: None", "status": "pending_approval"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Launching with
server_name="0.0.0.0"exposes the control panel on every network interface, so anyone on the same network/host namespace can access chat, memory, and training controls without additional protection. In environments where this app runs on a shared machine or VM, this is a security regression compared to a localhost-only default and should be restricted (or explicitly gated behind auth/config).Useful? React with 👍 / 👎.