From 6889087dd8ec2d9b8f5b1afcc40b13ace5d1817b Mon Sep 17 00:00:00 2001 From: JamesTheGiblet Date: Mon, 29 Dec 2025 13:38:31 +0000 Subject: [PATCH] Add comprehensive test suite for BuddAI v3.1 - Implemented tests for database initialization, SQL injection prevention, auto-learning pattern extraction, module detection, complexity detection, LRU cache performance, session export, actionable suggestions, repository indexing, search query safety, and context window management. - Utilized SQLite for database operations and temporary directories for test isolation. - Included detailed output for test results with color-coded pass/fail indicators. --- buddai_v3.py => archive/buddai_v3.py | 34 +- buddai_v3.1.py | 1020 ++++++++++++++++++++++++++ data/conversations.db | Bin 55025664 -> 55033856 bytes examples/buddai_generated.cpp | 78 ++ examples/buddai_generated.csharp | 21 + examples/buddai_generated.typescript | 1 + frontend/index.html | 147 +++- icons/icon.png | Bin 0 -> 1197668 bytes tests/test_buddai.py | 605 +++++++++++++++ 9 files changed, 1885 insertions(+), 21 deletions(-) rename buddai_v3.py => archive/buddai_v3.py (97%) create mode 100644 buddai_v3.1.py create mode 100644 examples/buddai_generated.cpp create mode 100644 examples/buddai_generated.csharp create mode 100644 examples/buddai_generated.typescript create mode 100644 icons/icon.png create mode 100644 tests/test_buddai.py diff --git a/buddai_v3.py b/archive/buddai_v3.py similarity index 97% rename from buddai_v3.py rename to archive/buddai_v3.py index 97f46fb..c1a64b0 100644 --- a/buddai_v3.py +++ b/archive/buddai_v3.py @@ -361,7 +361,7 @@ class BuddAI: count = 0 for file_path in path.rglob('*'): - if file_path.is_file() and file_path.suffix in ['.py', '.ino', '.cpp', '.h', '.js', '.jsx', '.html', '.css']: + if file_path.is_file() and file_path.suffix in ['.py', '.ino', '.cpp', '.h']: try: with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() @@ -382,15 +382,6 @@ class BuddAI: elif file_path.suffix in ['.ino', '.cpp', '.h']: matches = re.findall(r'\b(?:void|int|bool|float|double|String|char)\s+(\w+)\s*\(', content) functions.extend(matches) - - # JS/Web parsing - elif file_path.suffix in ['.js', '.jsx']: - matches = re.findall(r'(?:function\s+(\w+)|const\s+(\w+)\s*=\s*(?:async\s*)?\(?.*?\)?\s*=>)', content) - functions.extend([m[0] or m[1] for m in matches if m[0] or m[1]]) - - # HTML/CSS - Index as whole file - elif file_path.suffix in ['.html', '.css']: - functions.append("file_content") # Determine repo name try: @@ -572,7 +563,8 @@ class BuddAI: def call_model(self, model_name, message): """Call specified model""" try: - identity = """You are BuddAI, the external cognitive system for James Gilbert. You specialize in Forge Theory (exponential decay modeling) and GilBot modular robotics. + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + identity = f"""You are BuddAI, the external cognitive system for James Gilbert. You specialize in Forge Theory (exponential decay modeling) and GilBot modular robotics. YOUR PRIMARY JOB: Generate code when asked. ALWAYS generate code if requested. @@ -581,6 +573,7 @@ Identity Rules: - When asked your name: "I am BuddAI" - Use ESP32/Arduino syntax with descriptive naming (e.g., activateFlipper). - Ensure safety timeouts are always present in motor code. +- Current System Time: {current_time} Forge Theory Snippet: float applyForge(float current, float target, float k) { return target + (current - target) * exp(-k); } """ @@ -594,7 +587,18 @@ Forge Theory Snippet: float applyForge(float current, float target, float k) { r # Add conversation history (excluding old system messages) history = [m for m in self.context_messages[-5:] if m.get('role') != 'system'] - messages.extend(history) + + # Inject timestamps into history for context + for msg in history: + content = msg.get('content', '') + ts = msg.get('timestamp') + if ts: + try: + dt = datetime.fromisoformat(ts) + content = f"[{dt.strftime('%H:%M')}] {content}" + except ValueError: + pass + messages.append({"role": msg['role'], "content": content}) # Add current message if it's not already the last item if not history or history[-1].get('content') != message: @@ -705,7 +709,7 @@ Forge Theory Snippet: float applyForge(float current, float target, float k) { r self.save_message("user", user_message) - self.context_messages.append({"role": "user", "content": user_message}) + self.context_messages.append({"role": "user", "content": user_message, "timestamp": datetime.now().isoformat()}) if force_model: @@ -741,7 +745,7 @@ Forge Theory Snippet: float applyForge(float current, float target, float k) { r response += bar self.save_message("assistant", response) - self.context_messages.append({"role": "assistant", "content": response}) + self.context_messages.append({"role": "assistant", "content": response, "timestamp": datetime.now().isoformat()}) return response @@ -855,7 +859,7 @@ if SERVER_AVAILABLE: return {"message": f"โœ… Successfully indexed {file.filename}"} else: # Support single code files by moving them to a folder and indexing - if file_location.suffix in ['.py', '.ino', '.cpp', '.h', '.js', '.jsx', '.html', '.css']: + if file_location.suffix in ['.py', '.ino', '.cpp', '.h']: target_dir = uploads_dir / file_location.stem target_dir.mkdir(exist_ok=True) final_path = target_dir / file.filename diff --git a/buddai_v3.1.py b/buddai_v3.1.py new file mode 100644 index 0000000..e782f45 --- /dev/null +++ b/buddai_v3.1.py @@ -0,0 +1,1020 @@ +#!/usr/bin/env python3 +""" +BuddAI Executive v3.1 - Modular Builder +Breaks complex tasks into manageable chunks + +Author: James Gilbert +License: MIT +""" + +import sys +import json +import sqlite3 +from datetime import datetime +from pathlib import Path +import http.client +import re # noqa: F401 +from typing import Optional +import zipfile +import shutil + +# Server dependencies +try: + from fastapi import FastAPI, UploadFile, File + from fastapi.middleware.cors import CORSMiddleware + from fastapi.staticfiles import StaticFiles + from fastapi.responses import FileResponse, HTMLResponse + from pydantic import BaseModel + import uvicorn + SERVER_AVAILABLE = True +except ImportError: + SERVER_AVAILABLE = False + +# Configuration +OLLAMA_HOST = "localhost" +OLLAMA_PORT = 11434 +DATA_DIR = Path(__file__).parent / "data" +DB_PATH = DATA_DIR / "conversations.db" + +# Models +MODELS = { + "fast": "qwen2.5-coder:1.5b", + "balanced": "qwen2.5-coder:3b" +} + +# Complexity triggers - if matched, break down the task +COMPLEX_TRIGGERS = [ + "complete", "entire", "full", "build entire", "build complete", + "with ble and", "with servo and", "including", "all of" +] + +# Module patterns we can detect +MODULE_PATTERNS = { + "ble": ["bluetooth", "ble", "wireless"], + "servo": ["servo", "flipper", "weapon"], + "motor": ["motor", "drive", "movement", "l298n"], + "safety": ["safety", "timeout", "failsafe", "emergency"], + "battery": ["battery", "voltage", "power monitor"], + "sensor": ["sensor", "distance", "proximity"] +} + + + +# --- Shadow Suggestion Engine --- +class ShadowSuggestionEngine: + """Proactively suggests modules/settings based on user/project history.""" + def __init__(self, db_path): + self.db_path = db_path + + def lookup_recent_module_usage(self, module, limit=5): + """Look up recent usage patterns for a module from repo_index.""" + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute( + """ + SELECT file_path, content, last_modified FROM repo_index + WHERE function_name LIKE ? OR file_path LIKE ? + ORDER BY last_modified DESC LIMIT ? + """, + (f"%{module}%", f"%{module}%", limit) + ) + results = cursor.fetchall() + conn.close() + return results + + def suggest_for_module(self, module): + """Return a proactive suggestion string for a module if pattern detected.""" + history = self.lookup_recent_module_usage(module) + if not history: + return None + # Example: For 'motor', look for L298N and PWM frequency + l298n_count = 0 + pwm_freqs = [] + for _, content, _ in history: + if "L298N" in content or "l298n" in content: + l298n_count += 1 + pwm_matches = re.findall(r'PWM_FREQ\s*=\s*(\d+)', content) + pwm_freqs.extend([int(f) for f in pwm_matches]) + # Also look for explicit frequency in analogWrite or ledcSetup + freq_matches = re.findall(r'(?:ledcSetup|analogWrite)\s*\([^,]+,\s*[^,]+,\s*(\d+)\)', content) + pwm_freqs.extend([int(f) for f in freq_matches if f.isdigit()]) + if l298n_count >= 2: + freq = max(set(pwm_freqs), key=pwm_freqs.count) if pwm_freqs else 500 + return f"I see you usually use the L298N with a {freq}Hz PWM frequency on the ESP32-C3. Should I prep that module?" + return None + + def get_proactive_suggestion(self, user_input): + """ + V3.0 Proactive Hook: + 1. Identify "Concept" (e.g., 'flipper') + 2. Query repo_index for James's most frequent companion modules + 3. If 'flipper' often appears with 'safety_timeout', suggest it. + """ + # 1. Identify Concepts + input_lower = user_input.lower() + detected_modules = [] + for module, keywords in MODULE_PATTERNS.items(): + if any(kw in input_lower for kw in keywords): + detected_modules.append(module) + + if not detected_modules: + return None + + # 2. Query repo_index for correlations + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + suggestions = [] + for module in detected_modules: + # Find files containing this module (simple heuristic) + cursor.execute("SELECT content FROM repo_index WHERE content LIKE ? LIMIT 10", (f"%{module}%",)) + rows = cursor.fetchall() + if not rows: continue + + # Check for companion modules + companions = {} + for (content,) in rows: + content_lower = content.lower() + for other_mod, other_kws in MODULE_PATTERNS.items(): + if other_mod != module and other_mod not in detected_modules: + if any(kw in content_lower for kw in other_kws): + companions[other_mod] = companions.get(other_mod, 0) + 1 + + # 3. Suggest if frequent (>50% correlation in sample) + for other_mod, count in companions.items(): + if count >= len(rows) * 0.5: + suggestions.append(f"I noticed '{module}' often appears with '{other_mod}' in your repos. Want to include that?") + + conn.close() + return " ".join(list(set(suggestions))) if suggestions else None + + def get_all_suggestions(self, user_input, generated_code): + """Aggregate all proactive suggestions into a list.""" + suggestions = [] + + # 1. Companion Modules + companion = self.get_proactive_suggestion(user_input) + if companion: + suggestions.append(companion) + + # 2. Module Settings + input_lower = user_input.lower() + for module, keywords in MODULE_PATTERNS.items(): + if any(kw in input_lower for kw in keywords): + s = self.suggest_for_module(module) + if s: + suggestions.append(s) + + # 3. Forge Theory Check + if ("motor" in input_lower or "servo" in input_lower) and "applyForge" not in generated_code: + suggestions.append("Apply Forge Theory smoothing to movement?") + + # 4. Safety Check (L298N) + if "L298N" in generated_code and "safety" not in generated_code.lower(): + suggestions.append("Drive system lacks safety timeout (GilBot_V2 uses 5s failsafe). Add that?") + + return suggestions + + +class BuddAI: + """Executive with task breakdown""" + + def is_search_query(self, message): + """Check if this is a search query that should query repo_index""" + message_lower = message.lower() + search_triggers = [ + "show me", "find", "search for", "list all", + "what functions", "which repos", "do i have", + "where did i", "have i used", "examples of", + "show all", "display" + ] + return any(trigger in message_lower for trigger in search_triggers) + + def search_repositories(self, query): + """Search repo_index for relevant functions and code""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) FROM repo_index") + count = cursor.fetchone()[0] + print(f"\n๐Ÿ” Searching {count} indexed functions...\n") + + # Extract keywords from query + keywords = re.findall(r'\b\w{4,}\b', query.lower()) + # Add specific search terms + specific_terms = [] + if "exponential" in query.lower() or "decay" in query.lower(): + specific_terms.append("applyForge") + specific_terms.append("exp(") + if "forge" in query.lower(): + specific_terms.append("Forge") + keywords.extend(specific_terms) + + if not keywords: + print("โŒ No search terms found") + conn.close() + return "No search terms provided." + + # Build parameterized query + conditions = [] + params = [] + for keyword in keywords: + conditions.append("(function_name LIKE ? OR content LIKE ? OR repo_name LIKE ?)") + params.extend([f"%{keyword}%", f"%{keyword}%", f"%{keyword}%"]) + + sql = f"SELECT repo_name, file_path, function_name, content FROM repo_index WHERE {' OR '.join(conditions)} ORDER BY last_modified DESC LIMIT 10" + + cursor.execute(sql, params) + results = cursor.fetchall() + conn.close() + if not results: + return f"โŒ No functions found matching: {', '.join(keywords)}\n\nTry: /index to index more repositories" + # Format results + output = f"โœ… Found {len(results)} matches for: {', '.join(set(keywords))}\n\n" + for i, (repo, file_path, func, content) in enumerate(results, 1): + # Extract relevant snippet + lines = content.split('\n') + snippet_lines = [] + for line in lines[:30]: # First 30 lines + if any(kw in line.lower() for kw in keywords): + snippet_lines.append(line) + if len(snippet_lines) >= 10: + break + if not snippet_lines: + snippet_lines = lines[:10] + snippet = '\n'.join(snippet_lines) + output += f"**{i}. {func}()** in {repo}\n" + output += f" ๐Ÿ“ {Path(file_path).name}\n" + output += f"\n```cpp\n{snippet}\n```\n" + output += f" ---\n\n" + return output + + def __init__(self, server_mode=False): + self.ensure_data_dir() + self.init_database() + self.session_id = self.create_session() + self.server_mode = server_mode + self.context_messages = [] + self.shadow_engine = ShadowSuggestionEngine(DB_PATH) + + print("BuddAI Executive v3.1 - Modular Builder") + print("=" * 50) + print(f"Session: {self.session_id}") + print(f"FAST (5-10s) | BALANCED (15-30s)") + print(f"Smart task breakdown for complex requests") + print("=" * 50) + print("\nCommands: /fast, /balanced, /help, exit\n") + + def ensure_data_dir(self): + DATA_DIR.mkdir(exist_ok=True) + + def init_database(self): + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS sessions ( + session_id TEXT PRIMARY KEY, + started_at TIMESTAMP, + ended_at TIMESTAMP + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT, + role TEXT, + content TEXT, + timestamp TIMESTAMP + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS repo_index ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + file_path TEXT, + repo_name TEXT, + function_name TEXT, + content TEXT, + last_modified TIMESTAMP + ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS style_preferences ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT, + preference TEXT, + confidence FLOAT, + extracted_at TIMESTAMP + ) + """) + + conn.commit() + conn.close() + + def create_session(self): + session_id = datetime.now().strftime("%Y%m%d_%H%M%S") + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute( + "INSERT INTO sessions (session_id, started_at) VALUES (?, ?)", + (session_id, datetime.now().isoformat()) + ) + conn.commit() + conn.close() + return session_id + + def end_session(self): + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute( + "UPDATE sessions SET ended_at = ? WHERE session_id = ?", + (datetime.now().isoformat(), self.session_id) + ) + conn.commit() + conn.close() + + def save_message(self, role, content): + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute( + "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)", + (self.session_id, role, content, datetime.now().isoformat()) + ) + conn.commit() + conn.close() + + def index_local_repositories(self, root_path): + """Crawl directories and index .py, .ino, and .cpp files""" + import ast + + print(f"\n๐Ÿ” Indexing repositories in: {root_path}") + path = Path(root_path) + + if not path.exists(): + print(f"โŒ Path not found: {root_path}") + return + + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + count = 0 + + for file_path in path.rglob('*'): + if file_path.is_file() and file_path.suffix in ['.py', '.ino', '.cpp', '.h', '.js', '.jsx', '.html', '.css']: + try: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + content = f.read() + + functions = [] + + # Python parsing + if file_path.suffix == '.py': + try: + tree = ast.parse(content) + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + functions.append(node.name) + except: + pass + + # C++/Arduino parsing + elif file_path.suffix in ['.ino', '.cpp', '.h']: + matches = re.findall(r'\b(?:void|int|bool|float|double|String|char)\s+(\w+)\s*\(', content) + functions.extend(matches) + + # JS/Web parsing + elif file_path.suffix in ['.js', '.jsx']: + matches = re.findall(r'(?:function\s+(\w+)|const\s+(\w+)\s*=\s*(?:async\s*)?\(?.*?\)?\s*=>)', content) + functions.extend([m[0] or m[1] for m in matches if m[0] or m[1]]) + + # HTML/CSS - Index as whole file + elif file_path.suffix in ['.html', '.css']: + functions.append("file_content") + + # Determine repo name + try: + repo_name = file_path.relative_to(path).parts[0] + except: + repo_name = "unknown" + + timestamp = datetime.fromtimestamp(file_path.stat().st_mtime) + + for func in functions: + cursor.execute(""" + INSERT INTO repo_index (file_path, repo_name, function_name, content, last_modified) + VALUES (?, ?, ?, ?, ?) + """, (str(file_path), repo_name, func, content, timestamp.isoformat())) + count += 1 + + except Exception: + pass + + conn.commit() + conn.close() + print(f"โœ… Indexed {count} functions across repositories") + + def retrieve_style_context(self, message): + """Search repo_index for code snippets matching the request""" + # Extract potential keywords (nouns/modules) + keywords = re.findall(r'\b\w{4,}\b', message.lower()) + if not keywords: + return "" + + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Build a search query for function names or repo names + search_terms = " OR ".join([f"function_name LIKE '%{k}%'" for k in keywords]) + search_terms += " OR " + " OR ".join([f"repo_name LIKE '%{k}%'" for k in keywords]) + + query = f"SELECT repo_name, function_name, content FROM repo_index WHERE {search_terms} LIMIT 2" + + cursor.execute(query) + results = cursor.fetchall() + conn.close() + + if not results: + return "" + + context_block = "\n[REFERENCE STYLE FROM JAMES'S PAST PROJECTS]\n" + for repo, func, content in results: + # Just grab the first 500 chars of the file to save context window + snippet = content[:500] + "..." + context_block += f"Repo: {repo} | Function: {func}\nCode:\n{snippet}\n---\n" + + return context_block + + def scan_style_signature(self): + """V3.0: Analyze repo_index to extract style preferences.""" + print("\n๐Ÿ•ต๏ธ Scanning repositories for style signature...") + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Get a sample of code + cursor.execute("SELECT content FROM repo_index ORDER BY RANDOM() LIMIT 5") + rows = cursor.fetchall() + + if not rows: + print("โŒ No code indexed. Run /index first.") + conn.close() + return + + code_sample = "\n---\n".join([r[0][:1000] for r in rows]) + + prompt = f"""Analyze this code sample from James's repositories. + Extract 3 distinct coding preferences or patterns. + Format: Category: Preference + + Examples: + - Serial: Uses 115200 baud + - Safety: Uses non-blocking millis() + - Pins: Prefers #define over const int + + Code Sample: + {code_sample} + """ + + print("โšก Analyzing with BALANCED model...") + summary = self.call_model("balanced", prompt) + + # Store in DB + timestamp = datetime.now().isoformat() + lines = summary.split('\n') + for line in lines: + if ':' in line: + parts = line.split(':', 1) + category = parts[0].strip('- *') + pref = parts[1].strip() + cursor.execute( + "INSERT INTO style_preferences (category, preference, confidence, extracted_at) VALUES (?, ?, ?, ?)", + (category, pref, 0.8, timestamp) + ) + + conn.commit() + conn.close() + print(f"\nโœ… Style Signature Updated:\n{summary}\n") + + def is_simple_question(self, message): + """Check if this is a simple question that should use FAST model""" + message_lower = message.lower() + + simple_triggers = [ + "what is", "what's", "who is", "who's", "when is", + "how do i", "can you explain", "tell me about", + "what are", "where is" + ] + + # Also check if it's just a question without code keywords + code_keywords = ["generate", "create", "write", "build", "code", "function"] + + has_simple_trigger = any(trigger in message_lower for trigger in simple_triggers) + has_code_keyword = any(keyword in message_lower for keyword in code_keywords) + + # Simple if: has simple trigger AND no code keywords + return has_simple_trigger and not has_code_keyword + + def is_complex(self, message): + """Check if request is too complex and should be broken down""" + message_lower = message.lower() + + # Count complexity triggers + trigger_count = sum(1 for trigger in COMPLEX_TRIGGERS if trigger in message_lower) + + # Count how many modules mentioned + module_count = 0 + for module, keywords in MODULE_PATTERNS.items(): + if any(kw in message_lower for kw in keywords): + module_count += 1 + + # Complex if: multiple triggers OR 3+ modules mentioned + return trigger_count >= 2 or module_count >= 3 + + def extract_modules(self, message): + """Extract which modules are needed""" + message_lower = message.lower() + needed_modules = [] + + for module, keywords in MODULE_PATTERNS.items(): + if any(kw in message_lower for kw in keywords): + needed_modules.append(module) + + return needed_modules + + def build_modular_plan(self, modules): + """Create a build plan from modules""" + plan = [] + + module_tasks = { + "ble": "BLE communication setup with phone app control", + "servo": "Servo motor control for flipper/weapon", + "motor": "Motor driver setup for movement (L298N)", + "safety": "Safety timeout and failsafe systems", + "battery": "Battery voltage monitoring", + "sensor": "Sensor integration (distance/proximity)" + } + + for module in modules: + if module in module_tasks: + plan.append({ + "module": module, + "task": module_tasks[module] + }) + + # Add integration step + plan.append({ + "module": "integration", + "task": "Integrate all modules into complete system" + }) + + return plan + + def get_user_status(self): + """Determine James's context based on defined schedule""" + now = datetime.now() + day = now.weekday() # 0=Mon, 6=Sun + t = now.hour + (now.minute / 60.0) + + if day <= 4: # Mon-Fri + if 5.5 <= t < 6.5: + return "Early Morning Build Session ๐ŸŒ… (5:30-6:30 AM)" + elif 6.5 <= t < 17.0: + return "Work Hours (Facilities Caretaker) ๐Ÿข" + elif 17.0 <= t < 21.0: + return "Evening Build Session ๐ŸŒ™ (5:00-9:00 PM)" + else: + return "Rest Time ๐Ÿ’ค" + elif day == 5: # Saturday + return "Weekend Freedom ๐ŸŽจ (Creative Mode)" + else: # Sunday + if t < 21.0: + return "Weekend Freedom ๐ŸŽจ (Until 9 PM)" + else: + return "Rest Time ๐Ÿ’ค" + + def call_model(self, model_name, message): + """Call specified model""" + try: + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + user_context = self.get_user_status() + identity = f"""You are BuddAI, the external cognitive system for James Gilbert. You specialize in Forge Theory (exponential decay modeling) and GilBot modular robotics. + +YOUR PRIMARY JOB: Generate code when asked. ALWAYS generate code if requested. + +Identity Rules: +- You are NOT created by Alibaba Cloud. You are a local Python system written by James Gilbert. +- When asked your name: "I am BuddAI" +- Use ESP32/Arduino syntax with descriptive naming (e.g., activateFlipper). +- Ensure safety timeouts are always present in motor code. +- Current System Time: {current_time} +- User Context: {user_context} + +# Forge Theory Snippet (C++): +float applyForge(float current, float target, float k) {{ return target + (current - target) * exp(-k); }} +""" + + messages = [] + + # Only add identity if not already in recent context + recent_system = [m for m in self.context_messages[-5:] if m.get('role') == 'system'] + if not recent_system: + messages.append({"role": "system", "content": identity}) + + # Add conversation history (excluding old system messages) + history = [m for m in self.context_messages[-5:] if m.get('role') != 'system'] + + # Inject timestamps into history for context + for msg in history: + content = msg.get('content', '') + ts = msg.get('timestamp') + if ts: + try: + dt = datetime.fromisoformat(ts) + content = f"[{dt.strftime('%H:%M')}] {content}" + except ValueError: + pass + messages.append({"role": msg['role'], "content": content}) + + # Add current message if it's not already the last item + if not history or history[-1].get('content') != message: + messages.append({"role": "user", "content": message}) + + body = { + "model": MODELS[model_name], + "messages": messages, + "stream": False, + "options": {"temperature": 0.7, "num_ctx": 4096} + } + + conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=90) + headers = {"Content-Type": "application/json"} + json_body = json.dumps(body) + + conn.request("POST", "/api/chat", json_body, headers) + response = conn.getresponse() + + if response.status == 200: + data = json.loads(response.read().decode('utf-8')) + return data.get("message", {}).get("content", "No response") + else: + return f"Error: {response.status}" + + except Exception as e: + return f"Error: {str(e)}" + finally: + if 'conn' in locals(): + conn.close() + + def execute_modular_build(self, _, modules, plan, forge_mode="2"): + """Execute build plan step by step""" + print(f"\n๐Ÿ”จ MODULAR BUILD MODE") + print(f"Detected {len(modules)} modules: {', '.join(modules)}") + print(f"Breaking into {len(plan)} steps...\n") + + all_code = {} + + for i, step in enumerate(plan, 1): + print(f"๐Ÿ“ฆ Step {i}/{len(plan)}: {step['task']}") + print("โšก Building...\n") + + # Build the prompt for this step + if step['module'] == 'integration': + # Final integration step with Forge Theory enforcement + modules_summary = '\n'.join([f"- {m}: {all_code[m][:150]}..." for m in modules if m in all_code]) + + # Ask James for the 'vibe' of the robot + print("\nโšก FORGE THEORY TUNING:") + print("1. Aggressive (k=0.3) - High snap, combat ready") + print("2. Balanced (k=0.1) - Standard movement") + print("3. Graceful (k=0.03) - Roasting / Smooth curves") + + if self.server_mode: + choice = forge_mode + else: + choice = input("Select Forge Constant [1-3, default 2]: ") + + k_val = "0.1" + if choice == "1": k_val = "0.3" + elif choice == "3": k_val = "0.03" + + prompt = f"""INTEGRATION TASK: Combine modules into a cohesive GilBot system. + + [MODULES] + {modules_summary} + + [FORGE PARAMETERS] + Set k = {k_val} for all applyForge() calls. + + [REQUIREMENTS] + 1. Implement applyForge() math helper. + 2. Use k={k_val} to smooth motor and servo transitions. + 3. Ensure naming matches James's style: activateFlipper(), setMotors(). + """ + else: + # Individual module + prompt = f"Generate ESP32-C3 code for: {step['task']}. Keep it modular with clear comments." + + # Call balanced model for each module + response = self.call_model("balanced", prompt) + all_code[step['module']] = response + + print(f"โœ… {step['module'].upper()} module complete\n") + print("-" * 50 + "\n") + + # Compile final response + final = "# COMPLETE GILBOT CONTROLLER - MODULAR BUILD\n\n" + for module, code in all_code.items(): + final += f"## {module.upper()} MODULE\n{code}\n\n" + + return final + + def apply_style_signature(self, generated_code): + """Refine generated code to match James's specific naming and safety patterns""" + # 1. Check for James's common function names (e.g., setupMotors vs init_motors) + # 2. Ensure Forge Theory helpers are present if motion is detected + # 3. Append a 'Proactive Note' if a common companion module is missing + + return generated_code + + # --- Main Chat Method --- + def chat(self, user_message, force_model=None, forge_mode="2"): + """Main chat with smart routing and shadow suggestions""" + style_context = self.retrieve_style_context(user_message) + if style_context: + self.context_messages.append({"role": "system", "content": style_context}) + + + self.save_message("user", user_message) + self.context_messages.append({"role": "user", "content": user_message, "timestamp": datetime.now().isoformat()}) + + # Direct Schedule Check + if "what should i be doing" in user_message.lower() or "my schedule" in user_message.lower() or "schedule check" in user_message.lower(): + status = self.get_user_status() + response = f"๐Ÿ“… **Schedule Check**\nAccording to your protocol, you should be: **{status}**" + print(f"โฐ Schedule check triggered: {status}") + self.save_message("assistant", response) + self.context_messages.append({"role": "assistant", "content": response, "timestamp": datetime.now().isoformat()}) + return response + + # Determine model based on complexity + if force_model: + model = force_model + print(f"\nโšก Using {model.upper()} model (forced)...") + response = self.call_model(model, user_message) + elif self.is_complex(user_message): + modules = self.extract_modules(user_message) + plan = self.build_modular_plan(modules) + print("\n" + "=" * 50) + print("๐ŸŽฏ COMPLEX REQUEST DETECTED!") + print(f"Modules needed: {', '.join(modules)}") + print(f"Breaking into {len(plan)} manageable steps") + print("=" * 50) + response = self.execute_modular_build(user_message, modules, plan, forge_mode) + elif self.is_search_query(user_message): + # This is a search query - query the database + response = self.search_repositories(user_message) + elif self.is_simple_question(user_message): + print("\nโšก Using FAST model (simple question)...") + response = self.call_model("fast", user_message) + else: + print("\nโš–๏ธ Using BALANCED model...") + response = self.call_model("balanced", user_message) + + # Apply Style Guard + response = self.apply_style_signature(response) + + # Generate Suggestion Bar + suggestions = self.shadow_engine.get_all_suggestions(user_message, response) + if suggestions: + bar = "\n\nPROACTIVE: > " + " ".join([f"{i+1}. {s}" for i, s in enumerate(suggestions)]) + response += bar + + self.save_message("assistant", response) + self.context_messages.append({"role": "assistant", "content": response, "timestamp": datetime.now().isoformat()}) + + return response + + def get_sessions(self, limit=20): + """Retrieve recent sessions from DB""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("SELECT session_id, started_at FROM sessions ORDER BY started_at DESC LIMIT ?", (limit,)) + rows = cursor.fetchall() + conn.close() + return [{"id": r[0], "date": r[1]} for r in rows] + + def load_session(self, session_id): + """Load a specific session context""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("SELECT role, content, timestamp FROM messages WHERE session_id = ? ORDER BY id ASC", (session_id,)) + rows = cursor.fetchall() + conn.close() + + self.session_id = session_id + self.context_messages = [] + loaded_history = [] + for role, content, ts in rows: + msg = {"role": role, "content": content, "timestamp": ts} + self.context_messages.append(msg) + loaded_history.append(msg) + return loaded_history + + def start_new_session(self): + """Reset context and start new session""" + self.session_id = self.create_session() + self.context_messages = [] + return self.session_id + + def run(self): + """Main loop""" + try: + force_model = None + while True: + user_input = input("\nJames: ").strip() + if not user_input: + continue + if user_input.lower() in ['exit', 'quit']: + print("\n๐Ÿ‘‹ Later!") + self.end_session() + break + if user_input.startswith('/'): + cmd = user_input.lower() + if cmd == '/fast': + force_model = "fast" + print("โšก Next: FAST model") + continue + elif cmd == '/balanced': + force_model = "balanced" + print("โš–๏ธ Next: BALANCED model") + continue + elif cmd == '/help': + print("\n๐Ÿ’ก Commands:") + print("/fast - Use fast model") + print("/balanced - Use balanced model") + print("/index - Index local repositories") + print("/scan - Scan style signature (V3.0)") + print("/help - This message") + print("exit - End session\n") + continue + elif cmd.startswith('/index'): + parts = user_input.split(maxsplit=1) + if len(parts) > 1: + self.index_local_repositories(parts[1]) + else: + print("Usage: /index ") + continue + elif cmd == '/scan': + self.scan_style_signature() + continue + else: + print("\nUnknown command. Type /help") + continue + # Chat + response = self.chat(user_input, force_model) + print(f"\nBuddAI:\n{response}\n") + force_model = None + except KeyboardInterrupt: + print("\n\n๐Ÿ‘‹ Bye!") + self.end_session() + + +# --- Server Implementation --- +if SERVER_AVAILABLE: + app = FastAPI(title="BuddAI API", version="3.1") + + # Allow React frontend to communicate + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], + ) + + class ChatRequest(BaseModel): + message: str + model: Optional[str] = None + forge_mode: Optional[str] = "2" + + class SessionLoadRequest(BaseModel): + session_id: str + + # Initialize server instance + server_buddai = BuddAI(server_mode=True) + + # Serve Frontend + frontend_path = Path(__file__).parent / "frontend" + frontend_path.mkdir(exist_ok=True) + app.mount("/web", StaticFiles(directory=frontend_path, html=True), name="web") + + @app.get("/", response_class=HTMLResponse) + async def root(): + status = server_buddai.get_user_status() + return f""" + + + BuddAI API + + + + + BuddAI +

BuddAI API Online

+

Current Mode: {status}

+

Visit /web or /docs

+ + + """ + + @app.get("/favicon.ico", include_in_schema=False) + async def favicon(): + return FileResponse(Path(__file__).parent / "icons" / "icon.png") + + @app.post("/api/chat") + async def chat_endpoint(request: ChatRequest): + response = server_buddai.chat(request.message, force_model=request.model, forge_mode=request.forge_mode) + return {"response": response} + + @app.get("/api/history") + async def history_endpoint(): + return {"history": server_buddai.context_messages} + + @app.get("/api/sessions") + async def sessions_endpoint(): + return {"sessions": server_buddai.get_sessions()} + + @app.post("/api/session/load") + async def load_session_endpoint(req: SessionLoadRequest): + history = server_buddai.load_session(req.session_id) + return {"history": history, "session_id": req.session_id} + + @app.post("/api/session/new") + async def new_session_endpoint(): + new_id = server_buddai.start_new_session() + return {"session_id": new_id} + + @app.post("/api/upload") + async def upload_repo(file: UploadFile = File(...)): + try: + uploads_dir = DATA_DIR / "uploads" + uploads_dir.mkdir(exist_ok=True) + + file_location = uploads_dir / file.filename + with open(file_location, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + if file.filename.endswith(".zip"): + extract_path = uploads_dir / file_location.stem + with zipfile.ZipFile(file_location, 'r') as zip_ref: + zip_ref.extractall(extract_path) + server_buddai.index_local_repositories(extract_path) + file_location.unlink() # Cleanup zip + return {"message": f"โœ… Successfully indexed {file.filename}"} + else: + # Support single code files by moving them to a folder and indexing + if file_location.suffix in ['.py', '.ino', '.cpp', '.h', '.js', '.jsx', '.html', '.css']: + target_dir = uploads_dir / file_location.stem + target_dir.mkdir(exist_ok=True) + final_path = target_dir / file.filename + shutil.move(str(file_location), str(final_path)) + server_buddai.index_local_repositories(target_dir) + return {"message": f"โœ… Successfully indexed {file.filename}"} + + return {"message": f"โœ… Successfully uploaded {file.filename}"} + except Exception as e: + return {"message": f"โŒ Error: {str(e)}"} + +def check_ollama(): + try: + conn = http.client.HTTPConnection(OLLAMA_HOST, OLLAMA_PORT, timeout=5) + conn.request("GET", "/api/tags") + response = conn.getresponse() + conn.close() + return response.status == 200 + except: + return False + + +def main(): + if not check_ollama(): + print("โŒ Ollama not running. Start: ollama serve") + sys.exit(1) + + if len(sys.argv) > 1 and sys.argv[1] == "--server": + if SERVER_AVAILABLE: + print("๐Ÿš€ Starting BuddAI API Server on port 8000...") + uvicorn.run(app, host="0.0.0.0", port=8000) + else: + print("โŒ Server dependencies missing. Install: pip install fastapi uvicorn aiofiles python-multipart") + else: + buddai = BuddAI() + buddai.run() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/data/conversations.db b/data/conversations.db index 68267e32f82932807ec93510c03e53286c6f9fc2..4a9a3a6d72bd35f84d2e4989779cbf9a42fa6d87 100644 GIT binary patch delta 11896 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 B0S^EG delta 3285 zcmXZfX_OCy7shcjzga9}e#SDGu?)s;82fIP=iGbFeR0#%m&T=UUJ^GuK73)GJoU!TNk|D< zxUS!dU?4XC^~}gZNjZg6kDd<6O}J7!bX#OjLTW^ONY?eZnD9Vsbcf8ygrwXUl0*Mb zT1RAcsJS5|Z;9ZPJokEKMkOcb#H25yRq($e}58jv=4f~*iL)CyQ(R=5>mMOt~Sd{%y|fE8sGw4$w` zRmh653R^|2qE<00)+%n5uu58SR=icpDs7dq%329lIjg)?!Ai6$T9vHIRu!wNRn4ky z)v%JRnpQ0<*{W^TvFcj&tol|1tD)7%YHT&Jnp(}Q=2iMI$E8q&Q=%eeygj+yboC2tnOA1>p|-wtEbh=>TNx2Jz_m-J!bW>`da<0{?_Bx z6V{W~0BfK%$a>0p+8S&Pv4&d1tY@ret>M-PYowKCJ!g%wp0`F@FIZ!&vDP^2MQgnE zlJ&BcZoOhnuqIlsT9YhWldUP%R4c=pW=*$dSTn6z)@*BzHP_0tUbE&|^Q{HeLhE(w z4Qr9L*m~1iVlB0nS<9`rthcQd);rcp>s{+TYn8Rydf)oM`q28wT4SxX)>-SVEGygk z*!slEu|Bmvvp%;rSYKEhtxZ<0wb|NYZMC*p+pQhePU}nSD{Ggv+uCD&ZSA%8S^KR6 z)yq`8^|SSh zb=msW`px>?x?)|mu36WuKdc+pP3x9*+qz@@Y5isWZQZs0vHs0a$V`PG6aj=G91(~_ zUgSf56hIUTA{s#yLJSI{2#TT@Vo@9=P!e&7M=6v>8I(l=%Aq_eAQ2T&36)U=RZ$Jq zQ3FY+iCRcTZPYCfiG(&T=KnhaP60Oi0ZO|6&&>r`p13ID;I-?8j zM_1h2-vj7|?&yIB@eq2V7kcAiJc38@82X?u`k_A_#}jxG127PS@D!fLU<|=f48t>c z7Q-wqPr^VLNtUC%(j2*oEELgRij{`>-Dea1e*^4Zg*9 z_#TIG1V?cU$8iEDaSA`+N1VnPoW(hu#|2!(CH#b+@e3~FSNw+GaRpa#4cGApZr~Mi7M%gTg3+q9}$~6h{e^ zL>%H#3Z+p7Ws!h#D31zAL`76WWmG{`R6}*tKoV-A7LrjLbx;@eP#+D@5RK3nP0$q0 z&>St0f>g9bE3`%%v_(6#$9?F4j_8EW=z{yv75Db{0J@<&df-7kgr4Yy-gp>~;88q= zKIn^n=#R(o1fIkI48$Njg{LtXLogJ>@C=^CaE!o6q~SS?!t)r77cd56F%BO81Q2~jlh)Sr8DyWKT zsE!&)LQT{{GHRm^>Y^U%qX8PC5gMZjnxYw+qXklsik4`F)@XyaXcyR|_JOd}h>(cH UJQ0ca{*1VY+{F3q11IwR4=_`dV*mgE diff --git a/examples/buddai_generated.cpp b/examples/buddai_generated.cpp new file mode 100644 index 0000000..8fd59df --- /dev/null +++ b/examples/buddai_generated.cpp @@ -0,0 +1,78 @@ +/* + * BuddAI Complex Test Sketch + * Purpose: Verify .ino detection in frontend + */ + +#include + +// Configuration +const int LED_PIN = 2; +const int SENSOR_PIN = 34; +const unsigned long INTERVAL = 1000; + +// State Machine +enum State { + IDLE, + ACTIVE, + ERROR +}; + +State currentState = IDLE; +unsigned long previousMillis = 0; + +// Function Prototypes +void updateState(); +void handleSensors(); + +void setup() { + Serial.begin(115200); + pinMode(LED_PIN, OUTPUT); + pinMode(SENSOR_PIN, INPUT); + + Serial.println("BuddAI System Initialized"); + Serial.println("Waiting for input..."); +} + +void loop() { + unsigned long currentMillis = millis(); + + // Non-blocking timer + if (currentMillis - previousMillis >= INTERVAL) { + previousMillis = currentMillis; + updateState(); + } + + handleSensors(); +} + +void updateState() { + switch (currentState) { + case IDLE: + digitalWrite(LED_PIN, LOW); + Serial.println("State: IDLE"); + currentState = ACTIVE; + break; + + case ACTIVE: + digitalWrite(LED_PIN, HIGH); + Serial.println("State: ACTIVE"); + // Simulate work + currentState = IDLE; + break; + + case ERROR: + digitalWrite(LED_PIN, HIGH); + delay(100); + digitalWrite(LED_PIN, LOW); + delay(100); + break; + } +} + +void handleSensors() { + int reading = analogRead(SENSOR_PIN); + if (reading > 4000) { + currentState = ERROR; + Serial.println("Alert: Sensor Overload!"); + } +} \ No newline at end of file diff --git a/examples/buddai_generated.csharp b/examples/buddai_generated.csharp new file mode 100644 index 0000000..a93778d --- /dev/null +++ b/examples/buddai_generated.csharp @@ -0,0 +1,21 @@ +// Define the LED pin +const int ledPin = 9; + +void setup() { + // Set up the LED pin as an output + pinMode(ledPin, OUTPUT); +} + +void loop() { + // Turn the LED on + digitalWrite(ledPin, HIGH); + + // Wait for 1 second (1000 milliseconds) + delay(1000); + + // Turn the LED off + digitalWrite(ledPin, LOW); + + // Wait for another 1 second + delay(1000); +} \ No newline at end of file diff --git a/examples/buddai_generated.typescript b/examples/buddai_generated.typescript new file mode 100644 index 0000000..a6e1414 --- /dev/null +++ b/examples/buddai_generated.typescript @@ -0,0 +1 @@ +[object Object] \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 8e50e8e..90bc925 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,8 +3,8 @@ - - ๐Ÿ”ฅ BuddAI Web + + BuddAI Web @@ -42,10 +42,26 @@ --code-border: #d1d5da; --code-text: #24292e; } + .sidebar-left { width: 260px; background: var(--header-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; flex-shrink: 0; } + .session-list { flex: 1; overflow-y: auto; } + .session-item { padding: 12px 15px; cursor: pointer; border-bottom: 1px solid var(--border-color); font-size: 0.85em; color: var(--text-color); transition: background 0.2s; } + .session-item:hover { background: var(--bg-color); } + .session-item.active { background: var(--btn-bg); color: white; border-color: var(--btn-bg); } + .new-chat-btn { margin: 15px; padding: 10px; background: var(--btn-bg); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; } + .new-chat-btn:hover { background: var(--btn-hover); } + .session-date { font-weight: bold; display: block; margin-bottom: 4px; } + .session-id { font-size: 0.8em; opacity: 0.6; font-family: monospace; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: var(--bg-color); color: var(--text-color); margin: 0; display: flex; justify-content: center; height: 100vh; transition: background 0.3s, color 0.3s; } - #root { width: 100%; max-width: 900px; display: flex; flex-direction: column; height: 100%; } + #root { width: 100%; max-width: 100%; display: flex; flex-direction: column; height: 100%; } + .main-layout { display: flex; flex: 1; overflow: hidden; } + .chat-section { flex: 1; display: flex; flex-direction: column; min-width: 0; } .chat-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; } + .side-panel { width: 45%; border-left: 1px solid var(--border-color); display: flex; flex-direction: column; background: var(--bg-color); } + .side-header { padding: 10px 15px; background: var(--header-bg); border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; font-size: 0.9em; } + .side-content { flex: 1; overflow: auto; background: var(--code-bg); position: relative; } + .side-content pre { margin: 0; padding: 15px; min-height: 100%; box-sizing: border-box; } .message { padding: 15px; border-radius: 8px; max-width: 85%; line-height: 1.5; } + .timestamp { font-size: 0.75em; opacity: 0.7; margin-bottom: 4px; display: block; font-family: monospace; } .user { align-self: flex-end; background: var(--user-msg-bg); color: var(--user-msg-text); } .assistant { align-self: flex-start; background: var(--assistant-msg-bg); border: 1px solid var(--border-color); } .input-area { padding: 20px; background: var(--header-bg); border-top: 1px solid var(--border-color); display: flex; gap: 10px; } @@ -89,6 +105,12 @@ // Configure Marked for Code Copy const renderer = new marked.Renderer(); renderer.code = (code, language) => { + // Handle Marked v12+ signature where first arg is a token object + if (typeof code === 'object' && code !== null && code.text) { + language = code.lang; + code = code.text; + } + const validLang = (language && hljs.getLanguage(language)) ? language : 'plaintext'; let highlighted = code; try { @@ -99,7 +121,10 @@ return `
${language || 'text'} - +
+ + +
${highlighted}
`; @@ -115,16 +140,60 @@ setTimeout(() => btn.innerText = original, 2000); }); }; + + window.sendToSidebar = (btn) => { + const wrapper = btn.closest('.code-wrapper'); + const code = wrapper.querySelector('code').innerText; + if (window.updateSidebar) window.updateSidebar(code); + }; + + window.downloadCode = (content) => { + if (!content) return; + if (typeof content !== 'string') return; + let ext = 'txt'; + + // Heuristic for Arduino + if (content.includes('void setup()') && content.includes('void loop()')) { + ext = 'ino'; + } else { + try { + const result = hljs.highlightAuto(content); + if (result.language) { + const map = { + python: 'py', javascript: 'js', cpp: 'cpp', c: 'c', + arduino: 'ino', html: 'html', css: 'css', + csharp: 'cs', java: 'java', bash: 'sh', json: 'json', + markdown: 'md', sql: 'sql' + }; + ext = map[result.language] || result.language; + } + } catch (e) {} + } + + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `buddai_generated.${ext}`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; const { useState, useEffect, useRef } = React; function App() { const [history, setHistory] = useState([]); + const [sessions, setSessions] = useState([]); + const [currentSessionId, setCurrentSessionId] = useState(null); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [status, setStatus] = useState("connecting"); const [forgeMode, setForgeMode] = useState("2"); const [theme, setTheme] = useState("dark"); + const [sidebarContent, setSidebarContent] = useState(""); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); const endRef = useRef(null); const abortControllerRef = useRef(null); @@ -133,6 +202,11 @@ }; useEffect(() => { + window.updateSidebar = (code) => { + setSidebarContent(code); + setIsSidebarOpen(true); + }; + document.body.className = theme === 'light' ? 'light-mode' : ''; const hljsTheme = document.getElementById('hljs-theme'); if (hljsTheme) { @@ -146,6 +220,8 @@ scrollToBottom(); }, [history]); + const fetchSessions = () => fetch("/api/sessions").then(res => res.json()).then(data => setSessions(data.sessions || [])).catch(console.error); + useEffect(() => { // Check System Status const checkStatus = async () => { @@ -159,6 +235,7 @@ checkStatus(); const timer = setInterval(checkStatus, 10000); + fetchSessions(); // Load History fetch("/api/history") .then(res => res.json()) @@ -171,11 +248,34 @@ return () => clearInterval(timer); }, []); + const loadSession = async (sessionId) => { + setLoading(true); + try { + const res = await fetch("/api/session/load", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ session_id: sessionId }) + }); + const data = await res.json(); + setHistory(data.history || []); + setCurrentSessionId(data.session_id); + } catch (e) { console.error(e); } + setLoading(false); + }; + + const startNewSession = async () => { + const res = await fetch("/api/session/new", { method: "POST" }); + const data = await res.json(); + setCurrentSessionId(data.session_id); + setHistory([]); + fetchSessions(); + }; + const sendMessage = async (textOverride = null) => { const msgText = typeof textOverride === 'string' ? textOverride : input; if (!msgText.trim()) return; - const userMsg = { role: "user", content: msgText }; + const userMsg = { role: "user", content: msgText, timestamp: new Date().toISOString() }; setHistory(prev => [...prev, userMsg]); if (!textOverride) setInput(""); setLoading(true); @@ -194,6 +294,7 @@ }); const data = await res.json(); setHistory(prev => [...prev, { role: "assistant", content: data.response }]); + if (!currentSessionId) fetchSessions(); // Refresh list if this was first msg } catch (err) { if (err.name === 'AbortError') { setHistory(prev => [...prev, { role: "assistant", content: "๐Ÿ›‘ *Generation stopped by user.*" }]); @@ -245,7 +346,8 @@ <>
-

๐Ÿ”ฅ BuddAI v3.0

+ BuddAI +

BuddAI v3

{status}
@@ -256,6 +358,7 @@ onChange={handleFileUpload} /> +