Remove deprecated server files
This commit is contained in:
-61
@@ -1,61 +0,0 @@
|
|||||||
# Models & Large Files
|
|
||||||
.ollama/
|
|
||||||
models/
|
|
||||||
*.safetensors
|
|
||||||
*.gguf
|
|
||||||
*.bin
|
|
||||||
*.pth
|
|
||||||
|
|
||||||
# Workspace
|
|
||||||
workspace/
|
|
||||||
uploads/
|
|
||||||
backups/
|
|
||||||
|
|
||||||
# Python
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
*.pyo
|
|
||||||
*.egg-info/
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
.venv/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
|
|
||||||
# IDE
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Environment
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
logs/
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Temporary
|
|
||||||
*.tmp
|
|
||||||
*.cache
|
|
||||||
.pytest_cache/
|
|
||||||
.coverage
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
.PHONY: help install update monitor health-check backup clean reinstall logs
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "LLM Server - Comandos disponibles:"
|
|
||||||
@echo ""
|
|
||||||
@echo " make install - Instalar todo desde cero (requiere sudo)"
|
|
||||||
@echo " make update - Actualizar código y dependencias"
|
|
||||||
@echo " make monitor - Ver monitoreo en tiempo real"
|
|
||||||
@echo " make health-check - Verificar estado del sistema"
|
|
||||||
@echo " make backup - Hacer backup de configuración"
|
|
||||||
@echo " make logs - Ver logs de servicios"
|
|
||||||
@echo " make status - Ver estado de servicios"
|
|
||||||
@echo " make start - Iniciar servicios"
|
|
||||||
@echo " make stop - Detener servicios"
|
|
||||||
@echo " make restart - Reiniciar servicios"
|
|
||||||
@echo " make clean - Limpiar archivos temporales"
|
|
||||||
@echo " make reinstall - Reinstalar (redownload modelos)"
|
|
||||||
|
|
||||||
install:
|
|
||||||
@echo "Instalando LLM Server..."
|
|
||||||
sudo bash scripts/install.sh
|
|
||||||
|
|
||||||
install-quick:
|
|
||||||
@echo "Instalación rápida (sin modelos)..."
|
|
||||||
sudo bash scripts/install.sh --quick
|
|
||||||
|
|
||||||
install-gpu-only:
|
|
||||||
@echo "Instalando solo GPU drivers..."
|
|
||||||
sudo bash scripts/install.sh --gpu-only
|
|
||||||
|
|
||||||
update:
|
|
||||||
bash scripts/update.sh
|
|
||||||
|
|
||||||
monitor:
|
|
||||||
bash scripts/monitor.sh
|
|
||||||
|
|
||||||
health-check:
|
|
||||||
bash scripts/health-check.sh
|
|
||||||
|
|
||||||
backup:
|
|
||||||
bash scripts/backup.sh
|
|
||||||
|
|
||||||
logs:
|
|
||||||
@echo "Logs de Ollama:"
|
|
||||||
sudo journalctl -u ollama -f
|
|
||||||
|
|
||||||
logs-api:
|
|
||||||
@echo "Logs de API:"
|
|
||||||
sudo journalctl -u llm-api -f
|
|
||||||
|
|
||||||
status:
|
|
||||||
@echo "Ollama:"
|
|
||||||
@sudo systemctl status ollama --no-pager
|
|
||||||
@echo ""
|
|
||||||
@echo "LLM API:"
|
|
||||||
@sudo systemctl status llm-api --no-pager
|
|
||||||
|
|
||||||
start:
|
|
||||||
sudo systemctl start ollama llm-api
|
|
||||||
@echo "✅ Servicios iniciados"
|
|
||||||
|
|
||||||
stop:
|
|
||||||
sudo systemctl stop ollama llm-api
|
|
||||||
@echo "✅ Servicios detenidos"
|
|
||||||
|
|
||||||
restart:
|
|
||||||
sudo systemctl restart ollama llm-api
|
|
||||||
@echo "✅ Servicios reiniciados"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf __pycache__ .pytest_cache .venv venv
|
|
||||||
find . -type f -name "*.pyc" -delete
|
|
||||||
@echo "✅ Limpiado"
|
|
||||||
|
|
||||||
reinstall:
|
|
||||||
@echo "Reinstalando LLM Server..."
|
|
||||||
sudo bash scripts/install.sh --gpu-only
|
|
||||||
make update
|
|
||||||
@echo "✅ Reinstalación completada"
|
|
||||||
|
|
||||||
info:
|
|
||||||
@echo "========== INFORMACIÓN DEL SERVIDOR =========="
|
|
||||||
@echo "Hostname: $$(hostname)"
|
|
||||||
@echo "IP: $$(hostname -I)"
|
|
||||||
@echo "SO: $$(lsb_release -d | cut -f2)"
|
|
||||||
@echo "CPU: $$(lscpu | grep 'Model name' | cut -d: -f2)"
|
|
||||||
@echo "RAM: $$(free -h | awk 'NR==2 {print $$2}')"
|
|
||||||
@echo "GPU: $$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || echo 'No disponible')"
|
|
||||||
@echo "=============================================="
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
"""
|
|
||||||
Code Agent - Handles file operations, code execution, and testing
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
import subprocess
|
|
||||||
import webbrowser
|
|
||||||
import time
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Dict, Any
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# CONFIGURATION
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
LLM_SERVER = os.getenv("LLM_SERVER_URL", "http://localhost:8000")
|
|
||||||
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434")
|
|
||||||
LLM_FAST = os.getenv("LLM_FAST_MODEL", "phi")
|
|
||||||
LLM_POWER = os.getenv("LLM_POWER_MODEL", "deepseek-coder:33b")
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# CODE AGENT CLASS
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
class CodeAgent:
|
|
||||||
"""Agent that modifies files, executes code, and tests in browser"""
|
|
||||||
|
|
||||||
def __init__(self, work_dir: str = "./workspace"):
|
|
||||||
self.work_dir = Path(work_dir)
|
|
||||||
self.work_dir.mkdir(exist_ok=True)
|
|
||||||
print(f"📁 Workspace: {self.work_dir.absolute()}")
|
|
||||||
|
|
||||||
# ========================================================================
|
|
||||||
# FILE OPERATIONS
|
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
def read_file(self, file_path: str) -> str:
|
|
||||||
"""Read file from workspace"""
|
|
||||||
file = self.work_dir / file_path
|
|
||||||
|
|
||||||
if not file.exists():
|
|
||||||
return f"❌ File not found: {file_path}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
print(f"✅ Read: {file_path} ({len(content)} bytes)")
|
|
||||||
return content
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error reading {file_path}: {e}"
|
|
||||||
|
|
||||||
def write_file(self, file_path: str, content: str) -> str:
|
|
||||||
"""Write/modify file in workspace"""
|
|
||||||
file = self.work_dir / file_path
|
|
||||||
file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(content)
|
|
||||||
print(f"✅ Wrote: {file_path}")
|
|
||||||
return f"File saved: {file_path}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error writing {file_path}: {e}"
|
|
||||||
|
|
||||||
def delete_file(self, file_path: str) -> str:
|
|
||||||
"""Delete file from workspace"""
|
|
||||||
file = self.work_dir / file_path
|
|
||||||
|
|
||||||
if not file.exists():
|
|
||||||
return f"❌ File not found: {file_path}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
file.unlink()
|
|
||||||
return f"✅ Deleted: {file_path}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error deleting {file_path}: {e}"
|
|
||||||
|
|
||||||
def list_files(self) -> str:
|
|
||||||
"""List all files in workspace"""
|
|
||||||
files = list(self.work_dir.rglob("*"))
|
|
||||||
file_list = "\n".join([
|
|
||||||
f" {f.relative_to(self.work_dir)}"
|
|
||||||
for f in files if f.is_file()
|
|
||||||
])
|
|
||||||
return f"📁 Files in workspace:\n{file_list}" if file_list else "📁 No files"
|
|
||||||
|
|
||||||
# ========================================================================
|
|
||||||
# CODE EXECUTION
|
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
def execute_python(self, code: str, file_name: str = "exec.py") -> str:
|
|
||||||
"""Execute Python code"""
|
|
||||||
script_path = self.work_dir / file_name
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Save script
|
|
||||||
with open(script_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(code)
|
|
||||||
|
|
||||||
# Execute
|
|
||||||
result = subprocess.run(
|
|
||||||
["python3", str(script_path)],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=30,
|
|
||||||
cwd=self.work_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
output = result.stdout + result.stderr
|
|
||||||
print(f"🐍 Python executed: {file_name}")
|
|
||||||
return output[:1000] # Limit output
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
return "❌ Timeout: Script took more than 30 seconds"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
def execute_node(self, code: str, file_name: str = "exec.js") -> str:
|
|
||||||
"""Execute Node.js code"""
|
|
||||||
script_path = self.work_dir / file_name
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(script_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(code)
|
|
||||||
|
|
||||||
result = subprocess.run(
|
|
||||||
["node", str(script_path)],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=30,
|
|
||||||
cwd=self.work_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
output = result.stdout + result.stderr
|
|
||||||
print(f"📟 Node.js executed: {file_name}")
|
|
||||||
return output[:1000]
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
return "❌ Node.js not installed"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
def execute_shell(self, command: str) -> str:
|
|
||||||
"""Execute shell command (whitelist safe commands only)"""
|
|
||||||
allowed_commands = [
|
|
||||||
'ls', 'pwd', 'mkdir', 'rm', 'cp', 'mv',
|
|
||||||
'cat', 'grep', 'find', 'du', 'df',
|
|
||||||
'git', 'npm', 'pip', 'python'
|
|
||||||
]
|
|
||||||
|
|
||||||
cmd_name = command.split()[0] if command else ""
|
|
||||||
if cmd_name not in allowed_commands:
|
|
||||||
return f"❌ Command not allowed: {cmd_name}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
command.split(),
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=30,
|
|
||||||
cwd=self.work_dir
|
|
||||||
)
|
|
||||||
output = result.stdout + result.stderr
|
|
||||||
return output[:500]
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
# ========================================================================
|
|
||||||
# TESTING & BROWSER
|
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
def test_html(self, html_file: str) -> str:
|
|
||||||
"""Open HTML file in browser for testing"""
|
|
||||||
file_path = self.work_dir / html_file
|
|
||||||
|
|
||||||
if not file_path.exists():
|
|
||||||
return f"❌ File not found: {html_file}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = f"file:///{file_path.absolute()}".replace("\\", "/")
|
|
||||||
webbrowser.open(url)
|
|
||||||
print(f"🌐 Opened in browser: {html_file}")
|
|
||||||
return f"✅ HTML opened: {html_file}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
def open_browser(self, url: str) -> str:
|
|
||||||
"""Open URL in browser"""
|
|
||||||
try:
|
|
||||||
webbrowser.open(url)
|
|
||||||
return f"✅ Browser opened: {url}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
# ========================================================================
|
|
||||||
# GIT OPERATIONS
|
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
def git_commit(self, message: str = None) -> str:
|
|
||||||
"""Auto-commit changes"""
|
|
||||||
try:
|
|
||||||
if not message:
|
|
||||||
message = f"Auto-commit from agent - {datetime.now().isoformat()}"
|
|
||||||
|
|
||||||
# Git add
|
|
||||||
result = subprocess.run(
|
|
||||||
['git', 'add', '.'],
|
|
||||||
cwd=self.work_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
return f"❌ Git add failed: {result.stderr}"
|
|
||||||
|
|
||||||
# Git commit
|
|
||||||
result = subprocess.run(
|
|
||||||
['git', 'commit', '-m', message],
|
|
||||||
cwd=self.work_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
return f"ℹ️ Nothing to commit"
|
|
||||||
|
|
||||||
return f"✅ Committed: {message}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
def git_push(self) -> str:
|
|
||||||
"""Push to remote repository"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
['git', 'push'],
|
|
||||||
cwd=self.work_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=30
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
return "✅ Pushed to remote"
|
|
||||||
else:
|
|
||||||
return f"❌ Push failed: {result.stderr}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
# ========================================================================
|
|
||||||
# LLM INTERACTION
|
|
||||||
# ========================================================================
|
|
||||||
|
|
||||||
def call_llm(self, prompt: str, model: str = "phi") -> str:
|
|
||||||
"""Call LLM server"""
|
|
||||||
try:
|
|
||||||
response = requests.post(
|
|
||||||
f"{OLLAMA_URL}/api/generate",
|
|
||||||
json={
|
|
||||||
"model": model,
|
|
||||||
"prompt": prompt,
|
|
||||||
"stream": False
|
|
||||||
},
|
|
||||||
timeout=300
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()["response"]
|
|
||||||
else:
|
|
||||||
return f"❌ LLM Error: {response.status_code}"
|
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
return f"❌ Cannot connect to {OLLAMA_URL}"
|
|
||||||
except Exception as e:
|
|
||||||
return f"❌ Error: {e}"
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# GET AGENT TOOLS
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
def get_agent_tools(agent: CodeAgent) -> Dict[str, Any]:
|
|
||||||
"""Return dictionary of available tools"""
|
|
||||||
return {
|
|
||||||
"read_file": agent.read_file,
|
|
||||||
"write_file": agent.write_file,
|
|
||||||
"delete_file": agent.delete_file,
|
|
||||||
"execute_python": agent.execute_python,
|
|
||||||
"execute_node": agent.execute_node,
|
|
||||||
"execute_shell": agent.execute_shell,
|
|
||||||
"test_html": agent.test_html,
|
|
||||||
"open_browser": agent.open_browser,
|
|
||||||
"git_commit": agent.git_commit,
|
|
||||||
"git_push": agent.git_push,
|
|
||||||
"list_files": agent.list_files,
|
|
||||||
"call_llm": agent.call_llm,
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# MAIN (for testing)
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
agent = CodeAgent()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
print("\n🧪 Testing Agent...")
|
|
||||||
print(agent.list_files())
|
|
||||||
|
|
||||||
# Create test file
|
|
||||||
agent.write_file("test.txt", "Hello from agent!")
|
|
||||||
print(agent.read_file("test.txt"))
|
|
||||||
-247
@@ -1,247 +0,0 @@
|
|||||||
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
|
|
||||||
from fastapi.responses import StreamingResponse, JSONResponse
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
from fastapi.cors import CORSMiddleware
|
|
||||||
import aiofiles
|
|
||||||
import base64
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
import asyncio
|
|
||||||
import io
|
|
||||||
from PIL import Image
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
app = FastAPI(title="Code Agent Chat")
|
|
||||||
|
|
||||||
# CORS para que el frontend acceda
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_credentials=True,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Config
|
|
||||||
LLM_SERVER = os.getenv("LLM_SERVER_URL", "http://localhost:8000")
|
|
||||||
UPLOAD_DIR = Path("./uploads")
|
|
||||||
WORKSPACE_DIR = Path("./workspace")
|
|
||||||
UPLOAD_DIR.mkdir(exist_ok=True)
|
|
||||||
WORKSPACE_DIR.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
# Importar agent
|
|
||||||
from agent import CodeAgent, get_agent_tools
|
|
||||||
|
|
||||||
agent = CodeAgent(work_dir=str(WORKSPACE_DIR))
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# ENDPOINTS
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
@app.get("/api/health")
|
|
||||||
async def health():
|
|
||||||
"""Verificar que el servidor está vivo"""
|
|
||||||
return {"status": "ok", "workspace": str(WORKSPACE_DIR.absolute())}
|
|
||||||
|
|
||||||
@app.get("/api/files")
|
|
||||||
async def list_files():
|
|
||||||
"""Listar archivos en workspace"""
|
|
||||||
files = []
|
|
||||||
for f in WORKSPACE_DIR.rglob("*"):
|
|
||||||
if f.is_file():
|
|
||||||
files.append({
|
|
||||||
"name": str(f.relative_to(WORKSPACE_DIR)),
|
|
||||||
"size": f.stat().st_size,
|
|
||||||
"type": f.suffix
|
|
||||||
})
|
|
||||||
return {"files": files}
|
|
||||||
|
|
||||||
@app.get("/api/file/{file_path:path}")
|
|
||||||
async def read_file(file_path: str):
|
|
||||||
"""Leer contenido de archivo"""
|
|
||||||
file = WORKSPACE_DIR / file_path
|
|
||||||
|
|
||||||
if not file.exists():
|
|
||||||
raise HTTPException(status_code=404, detail="Archivo no encontrado")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
return {"content": content, "name": file_path}
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
@app.post("/api/chat")
|
|
||||||
async def chat(
|
|
||||||
message: str = Form(...),
|
|
||||||
image: UploadFile = File(None),
|
|
||||||
model: str = Form("phi")
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Endpoint principal de chat
|
|
||||||
- message: tu prompt
|
|
||||||
- image: imagen adjunta (opcional)
|
|
||||||
- model: "phi" (rápido) o "deepseek-coder:33b" (potente)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Procesar imagen si hay
|
|
||||||
image_data = None
|
|
||||||
image_base64 = None
|
|
||||||
|
|
||||||
if image:
|
|
||||||
try:
|
|
||||||
contents = await image.read()
|
|
||||||
image_base64 = base64.b64encode(contents).decode()
|
|
||||||
|
|
||||||
# Guardar imagen
|
|
||||||
image_path = UPLOAD_DIR / f"{uuid.uuid4()}.png"
|
|
||||||
with open(image_path, 'wb') as f:
|
|
||||||
f.write(contents)
|
|
||||||
|
|
||||||
image_data = f"[Imagen adjunta: {image.filename}]"
|
|
||||||
print(f"📸 Imagen procesada: {image.filename}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=400, detail=f"Error procesando imagen: {e}")
|
|
||||||
|
|
||||||
# Construir prompt para el LLM
|
|
||||||
full_prompt = f"""
|
|
||||||
{message}
|
|
||||||
|
|
||||||
{f"Contexto de imagen: {image_data}" if image_data else ""}
|
|
||||||
|
|
||||||
Tienes acceso a estas herramientas:
|
|
||||||
- read_file(file_path): Leer archivo
|
|
||||||
- write_file(file_path, content): Escribir archivo
|
|
||||||
- execute_python(code): Ejecutar Python
|
|
||||||
- execute_node(code): Ejecutar Node.js
|
|
||||||
- test_html(file_name): Servir y testear HTML
|
|
||||||
- open_browser(url): Abrir navegador
|
|
||||||
- list_files(): Listar archivos
|
|
||||||
|
|
||||||
Responde en JSON con tu pensamiento y la acción a ejecutar.
|
|
||||||
Ejemplo:
|
|
||||||
{{"thought": "Voy a leer el archivo", "action": "read_file", "args": {{"file_path": "app.js"}}}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Llamar LLM
|
|
||||||
try:
|
|
||||||
response = requests.post(
|
|
||||||
f"{LLM_SERVER}/code",
|
|
||||||
json={
|
|
||||||
"message": full_prompt,
|
|
||||||
"model": model
|
|
||||||
},
|
|
||||||
timeout=300,
|
|
||||||
stream=False
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=response.status_code,
|
|
||||||
detail=response.text
|
|
||||||
)
|
|
||||||
|
|
||||||
llm_response = response.json()["response"]
|
|
||||||
|
|
||||||
# Parsear JSON y ejecutar acción
|
|
||||||
result = process_agent_response(llm_response)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"message": message,
|
|
||||||
"image": image.filename if image else None,
|
|
||||||
"model": model,
|
|
||||||
"llm_response": llm_response,
|
|
||||||
"action_result": result,
|
|
||||||
"image_base64": image_base64
|
|
||||||
}
|
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=503,
|
|
||||||
detail=f"No se puede conectar a LLM Server: {LLM_SERVER}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
@app.post("/api/chat-stream")
|
|
||||||
async def chat_stream(
|
|
||||||
message: str = Form(...),
|
|
||||||
image: UploadFile = File(None),
|
|
||||||
model: str = Form("phi")
|
|
||||||
):
|
|
||||||
"""Versión streaming (respuestas en tiempo real)"""
|
|
||||||
|
|
||||||
image_data = None
|
|
||||||
if image:
|
|
||||||
contents = await image.read()
|
|
||||||
image_path = UPLOAD_DIR / f"{uuid.uuid4()}.png"
|
|
||||||
with open(image_path, 'wb') as f:
|
|
||||||
f.write(contents)
|
|
||||||
image_data = f"[Imagen: {image.filename}]"
|
|
||||||
|
|
||||||
full_prompt = f"{message}\n{image_data if image_data else ''}"
|
|
||||||
|
|
||||||
async def generate():
|
|
||||||
try:
|
|
||||||
# Streaming desde LLM
|
|
||||||
response = requests.post(
|
|
||||||
f"{LLM_SERVER}/code",
|
|
||||||
json={
|
|
||||||
"message": full_prompt,
|
|
||||||
"model": model
|
|
||||||
},
|
|
||||||
timeout=300,
|
|
||||||
stream=True
|
|
||||||
)
|
|
||||||
|
|
||||||
for line in response.iter_lines():
|
|
||||||
if line:
|
|
||||||
yield line + b'\n'
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
yield json.dumps({"error": str(e)}).encode() + b'\n'
|
|
||||||
|
|
||||||
return StreamingResponse(generate(), media_type="text/event-stream")
|
|
||||||
|
|
||||||
def process_agent_response(response: str) -> dict:
|
|
||||||
"""Parsear respuesta del LLM y ejecutar acción"""
|
|
||||||
tools = get_agent_tools(agent)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Extraer JSON
|
|
||||||
json_start = response.find('{')
|
|
||||||
json_end = response.rfind('}') + 1
|
|
||||||
json_str = response[json_start:json_end]
|
|
||||||
action = json.loads(json_str)
|
|
||||||
except:
|
|
||||||
return {"error": "No se pudo parsear la respuesta", "raw": response[:200]}
|
|
||||||
|
|
||||||
action_name = action.get("action") or action.get("tool")
|
|
||||||
args = action.get("args", {})
|
|
||||||
|
|
||||||
if action_name not in tools:
|
|
||||||
return {"error": f"Acción desconocida: {action_name}"}
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = tools[action_name](**args)
|
|
||||||
return {"success": True, "action": action_name, "result": result}
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": f"Error ejecutando {action_name}: {str(e)}"}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SERVIR STATIC FILES (UI Frontend)
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import uvicorn
|
|
||||||
print(f"🚀 Backend en http://localhost:5000")
|
|
||||||
print(f"📁 Workspace: {WORKSPACE_DIR.absolute()}")
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
{
|
|
||||||
"server": {
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"api_port": 8000,
|
|
||||||
"web_port": 5000,
|
|
||||||
"workers": 4,
|
|
||||||
"debug": false,
|
|
||||||
"environment": "production"
|
|
||||||
},
|
|
||||||
"models": {
|
|
||||||
"fast": {
|
|
||||||
"name": "phi",
|
|
||||||
"description": "Phi 2.7B - Rápido",
|
|
||||||
"vram_required_gb": 2,
|
|
||||||
"tokens_per_second": 50
|
|
||||||
},
|
|
||||||
"power": {
|
|
||||||
"name": "deepseek-coder:33b",
|
|
||||||
"description": "DeepSeek 33B - Potente",
|
|
||||||
"vram_required_gb": 18,
|
|
||||||
"tokens_per_second": 6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gpu": {
|
|
||||||
"memory_fraction": 0.9,
|
|
||||||
"max_batch_size": 8,
|
|
||||||
"enable_cuda": true,
|
|
||||||
"device_id": 0
|
|
||||||
},
|
|
||||||
"logging": {
|
|
||||||
"level": "INFO",
|
|
||||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
||||||
"log_dir": "./logs",
|
|
||||||
"max_file_size_mb": 10,
|
|
||||||
"backup_count": 10
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"type": "sqlite",
|
|
||||||
"path": "./data/llm_history.db",
|
|
||||||
"enable_history": true
|
|
||||||
},
|
|
||||||
"security": {
|
|
||||||
"enable_rate_limit": true,
|
|
||||||
"rate_limit_requests": 100,
|
|
||||||
"rate_limit_period_seconds": 60,
|
|
||||||
"require_api_key": false,
|
|
||||||
"allowed_origins": ["*"]
|
|
||||||
},
|
|
||||||
"workspace": {
|
|
||||||
"path": "./workspace",
|
|
||||||
"max_file_size_mb": 100,
|
|
||||||
"allowed_extensions": [".py", ".js", ".html", ".css", ".json", ".txt", ".md"]
|
|
||||||
},
|
|
||||||
"api_endpoints": {
|
|
||||||
"health": "/api/health",
|
|
||||||
"models": "/api/models",
|
|
||||||
"chat": "/api/chat",
|
|
||||||
"files": "/api/files",
|
|
||||||
"metrics": "/api/metrics"
|
|
||||||
},
|
|
||||||
"ollama": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 11434,
|
|
||||||
"timeout_seconds": 300,
|
|
||||||
"auto_download_models": true
|
|
||||||
},
|
|
||||||
"features": {
|
|
||||||
"enable_git_commit": true,
|
|
||||||
"enable_code_execution": true,
|
|
||||||
"enable_file_operations": true,
|
|
||||||
"enable_browser_testing": true,
|
|
||||||
"enable_metrics": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# API & Web Framework
|
|
||||||
fastapi==0.104.1
|
|
||||||
uvicorn[standard]==0.24.0
|
|
||||||
python-multipart==0.0.6
|
|
||||||
aiofiles==23.2.1
|
|
||||||
|
|
||||||
# LLM & AI
|
|
||||||
langchain==0.1.0
|
|
||||||
requests==2.31.0
|
|
||||||
|
|
||||||
# Data Processing
|
|
||||||
pillow==10.1.0
|
|
||||||
pydantic==2.5.0
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
python-dotenv==1.0.0
|
|
||||||
|
|
||||||
# Development (opcional)
|
|
||||||
pytest==7.4.3
|
|
||||||
pytest-asyncio==0.21.1
|
|
||||||
black==23.12.0
|
|
||||||
flake8==6.1.0
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# BACKUP SCRIPT
|
|
||||||
# Realiza backup de modelos y configuración
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
||||||
BACKUP_DIR="$PROJECT_DIR/backups"
|
|
||||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
||||||
|
|
||||||
log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $@"; }
|
|
||||||
log_success() { echo -e "${GREEN}✅ $@${NC}"; }
|
|
||||||
|
|
||||||
log "Iniciando backup..."
|
|
||||||
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
# Backup configuración
|
|
||||||
log "Haciendo backup de configuración..."
|
|
||||||
tar -czf "$BACKUP_DIR/config_$TIMESTAMP.tar.gz" \
|
|
||||||
-C "$PROJECT_DIR" \
|
|
||||||
config/ \
|
|
||||||
.env \
|
|
||||||
requirements.txt \
|
|
||||||
2>/dev/null || true
|
|
||||||
|
|
||||||
# Backup modelos (OPCIONAL - muy grandes)
|
|
||||||
read -p "¿Hacer backup de modelos Ollama? (y/n - muy grande, ~20GB): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
log "Haciendo backup de modelos (esto tardará)..."
|
|
||||||
tar -czf "$BACKUP_DIR/models_$TIMESTAMP.tar.gz" \
|
|
||||||
/home/charle/.ollama/models \
|
|
||||||
2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Backup workspace
|
|
||||||
log "Haciendo backup de workspace..."
|
|
||||||
tar -czf "$BACKUP_DIR/workspace_$TIMESTAMP.tar.gz" \
|
|
||||||
-C "$PROJECT_DIR" \
|
|
||||||
workspace/ \
|
|
||||||
2>/dev/null || true
|
|
||||||
|
|
||||||
log_success "Backups completados en: $BACKUP_DIR"
|
|
||||||
ls -lh "$BACKUP_DIR"
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# HEALTH CHECK SCRIPT
|
|
||||||
# Verifica que todo funcione correctamente
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
FAILURES=0
|
|
||||||
|
|
||||||
check() {
|
|
||||||
local name=$1
|
|
||||||
local cmd=$2
|
|
||||||
local expected=$3
|
|
||||||
|
|
||||||
echo -n "Verificando $name... "
|
|
||||||
|
|
||||||
if eval "$cmd" &>/dev/null; then
|
|
||||||
echo -e "${GREEN}✅${NC}"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌${NC}"
|
|
||||||
((FAILURES++))
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
echo -e "${BLUE}========================================${NC}"
|
|
||||||
echo -e "${BLUE} LLM SERVER HEALTH CHECK${NC}"
|
|
||||||
echo -e "${BLUE}========================================${NC}\n"
|
|
||||||
|
|
||||||
# Sistema
|
|
||||||
echo -e "${YELLOW}Sistema:${NC}"
|
|
||||||
check "Ubuntu 22.04" "grep -q '22.04' /etc/os-release"
|
|
||||||
check "Internet" "ping -c 1 8.8.8.8"
|
|
||||||
|
|
||||||
# Drivers & GPU
|
|
||||||
echo -e "\n${YELLOW}GPU & Drivers:${NC}"
|
|
||||||
check "NVIDIA Driver" "command -v nvidia-smi"
|
|
||||||
check "CUDA 12.3" "command -v nvcc"
|
|
||||||
check "RTX 3090" "nvidia-smi | grep -q 'RTX 3090'"
|
|
||||||
check "cuDNN" "ldconfig -p | grep -q cudnn"
|
|
||||||
|
|
||||||
# Software
|
|
||||||
echo -e "\n${YELLOW}Software:${NC}"
|
|
||||||
check "Python 3.11" "python3.11 --version"
|
|
||||||
check "Docker" "command -v docker"
|
|
||||||
check "Git" "command -v git"
|
|
||||||
|
|
||||||
# Services
|
|
||||||
echo -e "\n${YELLOW}Servicios:${NC}"
|
|
||||||
check "Ollama service" "sudo systemctl is-active ollama"
|
|
||||||
check "API service" "sudo systemctl is-active llm-api"
|
|
||||||
|
|
||||||
# Conectividad
|
|
||||||
echo -e "\n${YELLOW}API Connectivity:${NC}"
|
|
||||||
check "Ollama API" "curl -s http://localhost:11434/api/tags"
|
|
||||||
check "FastAPI" "curl -s http://localhost:8000/health"
|
|
||||||
|
|
||||||
# Modelos
|
|
||||||
echo -e "\n${YELLOW}Modelos Ollama:${NC}"
|
|
||||||
check "Phi disponible" "curl -s http://localhost:11434/api/tags | grep -q 'phi'"
|
|
||||||
check "DeepSeek disponible" "curl -s http://localhost:11434/api/tags | grep -q 'deepseek'"
|
|
||||||
|
|
||||||
# Disk Space
|
|
||||||
echo -e "\n${YELLOW}Espacio en Disco:${NC}"
|
|
||||||
root_usage=$(df / | awk 'NR==2 {print int($5)}')
|
|
||||||
if [ "$root_usage" -lt 90 ]; then
|
|
||||||
echo -e "Uso de disco (root): ${GREEN}${root_usage}%${NC}"
|
|
||||||
else
|
|
||||||
echo -e "Uso de disco (root): ${RED}${root_usage}%${NC}"
|
|
||||||
((FAILURES++))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
echo -e "\n${BLUE}========================================${NC}"
|
|
||||||
if [ "$FAILURES" -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✅ TODOS LOS CHECKS PASARON${NC}"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ $FAILURES CHECKS FALLARON${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,534 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# LLM SERVER DEPLOYMENT SCRIPT
|
|
||||||
# Ubuntu 22.04 LTS + ASUS H510M + RTX 3090
|
|
||||||
#
|
|
||||||
# Uso: bash scripts/install.sh [--quick] [--gpu-only]
|
|
||||||
#
|
|
||||||
# Opciones:
|
|
||||||
# --quick Salta verificaciones lentas
|
|
||||||
# --gpu-only Solo instala GPU drivers (para re-install)
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
set -e # Exit si hay error
|
|
||||||
|
|
||||||
# Colores
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
||||||
WORKSPACE_DIR="$PROJECT_DIR/workspace"
|
|
||||||
VENV_DIR="$PROJECT_DIR/venv"
|
|
||||||
LOG_FILE="$PROJECT_DIR/logs/install.log"
|
|
||||||
|
|
||||||
# Crear directorio de logs
|
|
||||||
mkdir -p "$PROJECT_DIR/logs"
|
|
||||||
|
|
||||||
# Function: Log con timestamp
|
|
||||||
log() {
|
|
||||||
local level=$1
|
|
||||||
shift
|
|
||||||
local message="$@"
|
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
echo -e "${BLUE}[$timestamp]${NC} ${level}: ${message}" | tee -a "$LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function: Log success
|
|
||||||
log_success() {
|
|
||||||
echo -e "${GREEN}✅ $@${NC}" | tee -a "$LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function: Log error
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}❌ $@${NC}" | tee -a "$LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function: Log warning
|
|
||||||
log_warning() {
|
|
||||||
echo -e "${YELLOW}⚠️ $@${NC}" | tee -a "$LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function: Check command exists
|
|
||||||
command_exists() {
|
|
||||||
command -v "$1" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function: Check if running as root
|
|
||||||
check_root() {
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
|
||||||
log_error "Este script debe ejecutarse con sudo"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# MAIN INSTALLATION
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
main() {
|
|
||||||
local quick_mode=false
|
|
||||||
local gpu_only=false
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case $1 in
|
|
||||||
--quick)
|
|
||||||
quick_mode=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--gpu-only)
|
|
||||||
gpu_only=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log_error "Opción desconocida: $1"
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
log "LOG" "=========================================="
|
|
||||||
log "LOG" "LLM Server Installation"
|
|
||||||
log "LOG" "=========================================="
|
|
||||||
log "LOG" "Project Dir: $PROJECT_DIR"
|
|
||||||
log "LOG" "Workspace: $WORKSPACE_DIR"
|
|
||||||
log "LOG" "Quick Mode: $quick_mode"
|
|
||||||
log "LOG" "GPU Only: $gpu_only"
|
|
||||||
log "LOG" "=========================================="
|
|
||||||
|
|
||||||
# Checks iniciales
|
|
||||||
check_root
|
|
||||||
check_os
|
|
||||||
check_hardware
|
|
||||||
|
|
||||||
if [ "$gpu_only" = false ]; then
|
|
||||||
install_dependencies
|
|
||||||
install_docker
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_nvidia_drivers
|
|
||||||
install_cuda_toolkit
|
|
||||||
install_ollama
|
|
||||||
setup_python_venv
|
|
||||||
create_services
|
|
||||||
|
|
||||||
if [ "$quick_mode" = false ]; then
|
|
||||||
download_models
|
|
||||||
fi
|
|
||||||
|
|
||||||
setup_directories
|
|
||||||
generate_config
|
|
||||||
|
|
||||||
log_success "=========================================="
|
|
||||||
log_success "✨ INSTALACIÓN COMPLETADA"
|
|
||||||
log_success "=========================================="
|
|
||||||
print_next_steps
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# FUNCIONES AUXILIARES
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
check_os() {
|
|
||||||
log "LOG" "Verificando Sistema Operativo..."
|
|
||||||
|
|
||||||
if [ ! -f /etc/os-release ]; then
|
|
||||||
log_error "No se pudo detectar el SO"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
. /etc/os-release
|
|
||||||
if [[ "$ID" != "ubuntu" ]]; then
|
|
||||||
log_error "Este script solo soporta Ubuntu"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$VERSION_ID" != "22.04" ]]; then
|
|
||||||
log_warning "Se recomienda Ubuntu 22.04, detectado: $VERSION_ID"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "Ubuntu $VERSION_ID detectado"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_hardware() {
|
|
||||||
log "LOG" "Verificando Hardware..."
|
|
||||||
|
|
||||||
# Check GPU
|
|
||||||
if ! command_exists nvidia-smi; then
|
|
||||||
log_warning "nvidia-smi no disponible aún (se instalará)"
|
|
||||||
else
|
|
||||||
gpu_info=$(nvidia-smi --query-gpu=name,memory.total --format=csv,noheader)
|
|
||||||
log_success "GPU detectada: $gpu_info"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check CPU
|
|
||||||
cpu_count=$(nproc)
|
|
||||||
log_success "CPU: $cpu_count cores"
|
|
||||||
|
|
||||||
# Check RAM
|
|
||||||
ram_gb=$(free -h | awk '/^Mem:/ {print $2}')
|
|
||||||
log_success "RAM: $ram_gb"
|
|
||||||
|
|
||||||
# Check Disk
|
|
||||||
disk_info=$(df -h / | awk 'NR==2 {print $2}')
|
|
||||||
log_success "Disco: $disk_info disponible"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_dependencies() {
|
|
||||||
log "LOG" "Instalando dependencias del sistema..."
|
|
||||||
|
|
||||||
apt update
|
|
||||||
apt install -y \
|
|
||||||
build-essential \
|
|
||||||
git \
|
|
||||||
wget \
|
|
||||||
curl \
|
|
||||||
htop \
|
|
||||||
nano \
|
|
||||||
openssh-server \
|
|
||||||
python3.11 \
|
|
||||||
python3.11-venv \
|
|
||||||
python3.11-dev \
|
|
||||||
pkg-config \
|
|
||||||
libssl-dev \
|
|
||||||
libffi-dev
|
|
||||||
|
|
||||||
log_success "Dependencias instaladas"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_docker() {
|
|
||||||
log "LOG" "Instalando Docker..."
|
|
||||||
|
|
||||||
if command_exists docker; then
|
|
||||||
log_success "Docker ya está instalado"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
curl -fsSL https://get.docker.com -o /tmp/get-docker.sh
|
|
||||||
sh /tmp/get-docker.sh
|
|
||||||
|
|
||||||
# Agregar usuario al grupo docker
|
|
||||||
if id "charle" &>/dev/null; then
|
|
||||||
usermod -aG docker charle
|
|
||||||
log_success "Usuario 'charle' agregado al grupo docker"
|
|
||||||
fi
|
|
||||||
|
|
||||||
systemctl start docker
|
|
||||||
systemctl enable docker
|
|
||||||
|
|
||||||
log_success "Docker instalado"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_nvidia_drivers() {
|
|
||||||
log "LOG" "Instalando NVIDIA Drivers..."
|
|
||||||
|
|
||||||
if command_exists nvidia-smi; then
|
|
||||||
current_driver=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -1)
|
|
||||||
log_success "Driver NVIDIA $current_driver ya instalado"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Agregar repositorio NVIDIA
|
|
||||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A4B469963BF863CC 2>&1 | grep -v "Warning" || true
|
|
||||||
|
|
||||||
add-apt-repository "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ /" 2>&1 | grep -v "already" || true
|
|
||||||
|
|
||||||
apt update
|
|
||||||
apt install -y cuda-drivers
|
|
||||||
|
|
||||||
log_success "NVIDIA Drivers instalados"
|
|
||||||
|
|
||||||
log_warning "Se recomienda reiniciar: sudo reboot"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_cuda_toolkit() {
|
|
||||||
log "LOG" "Instalando CUDA Toolkit 12.3..."
|
|
||||||
|
|
||||||
if [ -d "/usr/local/cuda-12.3" ]; then
|
|
||||||
log_success "CUDA 12.3 ya está instalado"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "LOG" "Descargando CUDA 12.3.0..."
|
|
||||||
cd /tmp
|
|
||||||
wget -q https://developer.download.nvidia.com/compute/cuda/12.3.0/local_installers/cuda_12.3.0_545.23.06_linux.run \
|
|
||||||
-O cuda_12.3.0_545.23.06_linux.run
|
|
||||||
|
|
||||||
log "LOG" "Instalando CUDA (esto puede tardar 10-15 minutos)..."
|
|
||||||
chmod +x cuda_12.3.0_545.23.06_linux.run
|
|
||||||
./cuda_12.3.0_545.23.06_linux.run --silent --driver=false --toolkit
|
|
||||||
|
|
||||||
# Configurar PATH
|
|
||||||
if ! grep -q "cuda-12.3" /root/.bashrc; then
|
|
||||||
cat >> /root/.bashrc << 'EOF'
|
|
||||||
|
|
||||||
# CUDA 12.3
|
|
||||||
export PATH=/usr/local/cuda-12.3/bin:$PATH
|
|
||||||
export LD_LIBRARY_PATH=/usr/local/cuda-12.3/lib64:$LD_LIBRARY_PATH
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
source /root/.bashrc
|
|
||||||
|
|
||||||
# Verificar
|
|
||||||
if command_exists nvcc; then
|
|
||||||
cuda_version=$(nvcc --version | grep "release" | awk '{print $5}')
|
|
||||||
log_success "CUDA $cuda_version instalado"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install cuDNN
|
|
||||||
log "LOG" "Instalando cuDNN..."
|
|
||||||
apt install -y libcudnn8
|
|
||||||
|
|
||||||
log_success "CUDA Toolkit instalado"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_ollama() {
|
|
||||||
log "LOG" "Instalando Ollama..."
|
|
||||||
|
|
||||||
if command_exists ollama; then
|
|
||||||
log_success "Ollama ya está instalado"
|
|
||||||
else
|
|
||||||
curl https://ollama.ai/install.sh | sh
|
|
||||||
log_success "Ollama instalado"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configurar servicio systemd
|
|
||||||
log "LOG" "Configurando servicio Ollama..."
|
|
||||||
|
|
||||||
mkdir -p /etc/systemd/system
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/ollama.service << 'EOF'
|
|
||||||
[Unit]
|
|
||||||
Description=Ollama
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/local/bin/ollama serve
|
|
||||||
User=charle
|
|
||||||
Group=charle
|
|
||||||
Restart=always
|
|
||||||
RestartSec=3
|
|
||||||
Environment="OLLAMA_HOST=0.0.0.0:11434"
|
|
||||||
Environment="OLLAMA_MODELS=/home/charle/.ollama/models"
|
|
||||||
Environment="OLLAMA_NUM_GPU=1"
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable ollama
|
|
||||||
systemctl restart ollama
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
if systemctl is-active --quiet ollama; then
|
|
||||||
log_success "Servicio Ollama activo"
|
|
||||||
else
|
|
||||||
log_error "Error iniciando Ollama"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_python_venv() {
|
|
||||||
log "LOG" "Creando Python Virtual Environment..."
|
|
||||||
|
|
||||||
if [ -d "$VENV_DIR" ]; then
|
|
||||||
log_success "VEnv ya existe"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
python3.11 -m venv "$VENV_DIR"
|
|
||||||
|
|
||||||
source "$VENV_DIR/bin/activate"
|
|
||||||
|
|
||||||
pip install --upgrade pip setuptools wheel
|
|
||||||
|
|
||||||
pip install \
|
|
||||||
fastapi \
|
|
||||||
uvicorn \
|
|
||||||
python-multipart \
|
|
||||||
aiofiles \
|
|
||||||
pillow \
|
|
||||||
python-dotenv \
|
|
||||||
requests \
|
|
||||||
langchain \
|
|
||||||
pydantic
|
|
||||||
|
|
||||||
log_success "Python VEnv configurado"
|
|
||||||
}
|
|
||||||
|
|
||||||
create_services() {
|
|
||||||
log "LOG" "Creando systemd services..."
|
|
||||||
|
|
||||||
# API Service
|
|
||||||
cat > /etc/systemd/system/llm-api.service << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=LLM API Server
|
|
||||||
After=network.target ollama.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=charle
|
|
||||||
WorkingDirectory=$PROJECT_DIR
|
|
||||||
Environment="PATH=$VENV_DIR/bin"
|
|
||||||
Environment="OLLAMA_URL=http://localhost:11434"
|
|
||||||
ExecStart=$VENV_DIR/bin/python $PROJECT_DIR/backend.py
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable llm-api
|
|
||||||
|
|
||||||
log_success "Systemd services creados"
|
|
||||||
}
|
|
||||||
|
|
||||||
download_models() {
|
|
||||||
log "LOG" "Descargando modelos (esto puede tardar 20-30 minutos)..."
|
|
||||||
log_warning "Esto es OPCIONAL. Presiona Ctrl+C para cancelar."
|
|
||||||
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
source "$VENV_DIR/bin/activate"
|
|
||||||
|
|
||||||
# Esperar a que Ollama esté listo
|
|
||||||
for i in {1..30}; do
|
|
||||||
if curl -s http://localhost:11434/api/tags > /dev/null; then
|
|
||||||
log_success "Ollama está listo"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
log "LOG" "Esperando Ollama... ($i/30)"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
log "LOG" "Descargando Phi..."
|
|
||||||
ollama pull phi:latest
|
|
||||||
|
|
||||||
log "LOG" "Descargando DeepSeek Coder 33B..."
|
|
||||||
ollama pull deepseek-coder:33b
|
|
||||||
|
|
||||||
log_success "Modelos descargados"
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_directories() {
|
|
||||||
log "LOG" "Creando directorios..."
|
|
||||||
|
|
||||||
mkdir -p "$WORKSPACE_DIR"
|
|
||||||
mkdir -p "$PROJECT_DIR/uploads"
|
|
||||||
mkdir -p "$PROJECT_DIR/logs"
|
|
||||||
|
|
||||||
# Cambiar permisos
|
|
||||||
chown -R charle:charle "$PROJECT_DIR"
|
|
||||||
chmod -R 755 "$PROJECT_DIR"
|
|
||||||
|
|
||||||
log_success "Directorios creados"
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_config() {
|
|
||||||
log "LOG" "Generando archivos de configuración..."
|
|
||||||
|
|
||||||
# .env file
|
|
||||||
if [ ! -f "$PROJECT_DIR/.env" ]; then
|
|
||||||
cat > "$PROJECT_DIR/.env" << EOF
|
|
||||||
# LLM Server Configuration
|
|
||||||
LLM_SERVER_URL=http://localhost:8000
|
|
||||||
LLM_FAST_MODEL=phi
|
|
||||||
LLM_POWER_MODEL=deepseek-coder:33b
|
|
||||||
OLLAMA_URL=http://localhost:11434
|
|
||||||
WORKSPACE_DIR=$WORKSPACE_DIR
|
|
||||||
|
|
||||||
# Server
|
|
||||||
HOST=0.0.0.0
|
|
||||||
API_PORT=8000
|
|
||||||
WEB_PORT=5000
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
LOG_LEVEL=INFO
|
|
||||||
EOF
|
|
||||||
log_success ".env creado"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Config JSON
|
|
||||||
if [ ! -f "$PROJECT_DIR/config/server.json" ]; then
|
|
||||||
mkdir -p "$PROJECT_DIR/config"
|
|
||||||
cat > "$PROJECT_DIR/config/server.json" << 'EOF'
|
|
||||||
{
|
|
||||||
"server": {
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"api_port": 8000,
|
|
||||||
"web_port": 5000,
|
|
||||||
"workers": 4
|
|
||||||
},
|
|
||||||
"models": {
|
|
||||||
"fast": "phi",
|
|
||||||
"power": "deepseek-coder:33b"
|
|
||||||
},
|
|
||||||
"gpu": {
|
|
||||||
"memory_fraction": 0.9,
|
|
||||||
"max_batch_size": 8
|
|
||||||
},
|
|
||||||
"logging": {
|
|
||||||
"level": "INFO",
|
|
||||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
log_success "config/server.json creado"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
print_next_steps() {
|
|
||||||
cat << EOF
|
|
||||||
|
|
||||||
${BLUE}=========================================${NC}
|
|
||||||
${GREEN}✨ PRÓXIMOS PASOS:${NC}
|
|
||||||
${BLUE}=========================================${NC}
|
|
||||||
|
|
||||||
1. ${YELLOW}Verificar instalación:${NC}
|
|
||||||
sudo systemctl status ollama
|
|
||||||
sudo systemctl status llm-api
|
|
||||||
|
|
||||||
2. ${YELLOW}Ver logs:${NC}
|
|
||||||
sudo journalctl -u ollama -f
|
|
||||||
sudo journalctl -u llm-api -f
|
|
||||||
|
|
||||||
3. ${YELLOW}Iniciar servicios:${NC}
|
|
||||||
sudo systemctl start ollama
|
|
||||||
sudo systemctl start llm-api
|
|
||||||
|
|
||||||
4. ${YELLOW}Acceder a la API:${NC}
|
|
||||||
curl http://localhost:8000/health
|
|
||||||
|
|
||||||
5. ${YELLOW}Descargar modelos (si no se descargaron):${NC}
|
|
||||||
source $VENV_DIR/bin/activate
|
|
||||||
ollama pull phi:latest
|
|
||||||
ollama pull deepseek-coder:33b
|
|
||||||
|
|
||||||
6. ${YELLOW}Ver estado en tiempo real:${NC}
|
|
||||||
watch -n 1 nvidia-smi
|
|
||||||
|
|
||||||
${BLUE}=========================================${NC}
|
|
||||||
${GREEN}📁 Archivos importantes:${NC}
|
|
||||||
${BLUE}=========================================${NC}
|
|
||||||
Config: $PROJECT_DIR/config/server.json
|
|
||||||
.env: $PROJECT_DIR/.env
|
|
||||||
Logs: $PROJECT_DIR/logs/
|
|
||||||
Workspace: $WORKSPACE_DIR/
|
|
||||||
|
|
||||||
${BLUE}=========================================${NC}
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ejecutar main
|
|
||||||
main "$@"
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# MONITOR SCRIPT
|
|
||||||
# Monitorea servicios y hardware en tiempo real
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
clear_screen() {
|
|
||||||
clear
|
|
||||||
}
|
|
||||||
|
|
||||||
print_header() {
|
|
||||||
echo -e "${BLUE}=================================================${NC}"
|
|
||||||
echo -e "${BLUE} LLM SERVER MONITOR - $(date '+%Y-%m-%d %H:%M:%S')${NC}"
|
|
||||||
echo -e "${BLUE}=================================================${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_section() {
|
|
||||||
echo -e "\n${YELLOW}>>> $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_service() {
|
|
||||||
local service=$1
|
|
||||||
if sudo systemctl is-active --quiet "$service"; then
|
|
||||||
echo -e "${GREEN}✅ $service - ACTIVO${NC}"
|
|
||||||
sudo systemctl status "$service" --no-pager | grep -E "(Active|ExecStart)" | sed 's/^/ /'
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ $service - INACTIVO${NC}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
get_ip() {
|
|
||||||
hostname -I | awk '{print $1}'
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
while true; do
|
|
||||||
clear_screen
|
|
||||||
print_header
|
|
||||||
|
|
||||||
# System Info
|
|
||||||
print_section "SISTEMA"
|
|
||||||
echo "Hostname: $(hostname)"
|
|
||||||
echo "IP: $(get_ip)"
|
|
||||||
echo "Uptime: $(uptime -p)"
|
|
||||||
echo "Usuarios conectados: $(who | wc -l)"
|
|
||||||
|
|
||||||
# CPU & RAM
|
|
||||||
print_section "CPU & MEMORIA"
|
|
||||||
free -h | awk 'NR==1 {print ""; print $0} NR==2 {print $0}'
|
|
||||||
echo ""
|
|
||||||
top -bn1 | head -3 | tail -1
|
|
||||||
|
|
||||||
# Disk
|
|
||||||
print_section "DISCO"
|
|
||||||
df -h / | awk 'NR==2 {printf "Root: %s used / %s total (%.1f%%)\n", $3, $2, ($3/$2)*100}'
|
|
||||||
|
|
||||||
# GPU
|
|
||||||
print_section "GPU - NVIDIA RTX 3090"
|
|
||||||
nvidia-smi --query-gpu=index,name,driver_version,memory.used,memory.total,temperature.gpu,utilization.gpu \
|
|
||||||
--format=csv,noheader | while read line; do
|
|
||||||
echo " $line"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Services
|
|
||||||
print_section "SERVICIOS"
|
|
||||||
check_service "ollama"
|
|
||||||
echo ""
|
|
||||||
check_service "llm-api"
|
|
||||||
|
|
||||||
# Network
|
|
||||||
print_section "RED"
|
|
||||||
echo "API (port 8000): $(curl -s http://localhost:8000/health | jq '.' 2>/dev/null || echo 'NO RESPONDE')"
|
|
||||||
echo "Ollama (port 11434): $(curl -s http://localhost:11434/api/tags | jq '.models | length' 2>/dev/null || echo '0') modelos"
|
|
||||||
|
|
||||||
# Logs recientes
|
|
||||||
print_section "ÚLTIMOS ERRORES (últimas 5 líneas)"
|
|
||||||
echo "Ollama:"
|
|
||||||
sudo journalctl -u ollama -n 3 --no-pager | sed 's/^/ /'
|
|
||||||
echo ""
|
|
||||||
echo "API:"
|
|
||||||
sudo journalctl -u llm-api -n 3 --no-pager | sed 's/^/ /'
|
|
||||||
|
|
||||||
# Footer
|
|
||||||
echo ""
|
|
||||||
echo -e "${BLUE}=================================================${NC}"
|
|
||||||
echo "Presiona Ctrl+C para salir | Se actualiza cada 10 segundos"
|
|
||||||
echo -e "${BLUE}=================================================${NC}"
|
|
||||||
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
main
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# UPDATE SCRIPT
|
|
||||||
# Actualiza código, modelos y dependencias
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
||||||
VENV_DIR="$PROJECT_DIR/venv"
|
|
||||||
|
|
||||||
log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $@"; }
|
|
||||||
log_success() { echo -e "${GREEN}✅ $@${NC}"; }
|
|
||||||
log_error() { echo -e "${RED}❌ $@${NC}"; exit 1; }
|
|
||||||
|
|
||||||
log "Actualizando LLM Server..."
|
|
||||||
|
|
||||||
# Pull latest from git
|
|
||||||
log "Descargando cambios de git..."
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
git pull origin main || log "Git pull completado con warnings"
|
|
||||||
|
|
||||||
# Update Python dependencies
|
|
||||||
log "Actualizando dependencias Python..."
|
|
||||||
source "$VENV_DIR/bin/activate"
|
|
||||||
pip install --upgrade pip
|
|
||||||
pip install -r requirements.txt --upgrade
|
|
||||||
|
|
||||||
# Update Ollama models (opcional)
|
|
||||||
read -p "¿Actualizar modelos Ollama? (y/n): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
log "Actualizando Phi..."
|
|
||||||
ollama pull phi:latest
|
|
||||||
|
|
||||||
log "Actualizando DeepSeek..."
|
|
||||||
ollama pull deepseek-coder:33b
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Restart services
|
|
||||||
log "Reiniciando servicios..."
|
|
||||||
sudo systemctl restart ollama
|
|
||||||
sudo systemctl restart llm-api
|
|
||||||
|
|
||||||
log_success "Actualización completada"
|
|
||||||
@@ -1,549 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="es">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Code Agent Chat</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chat Panel */
|
|
||||||
.chat-panel {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-header {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-messages {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
animation: slideIn 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideIn {
|
|
||||||
from { opacity: 0; transform: translateY(10px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.user {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content {
|
|
||||||
max-width: 70%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: 12px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.user .message-content {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.assistant .message-content {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-image {
|
|
||||||
max-width: 200px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-meta {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Input Area */
|
|
||||||
.input-area {
|
|
||||||
border-top: 1px solid #e0e0e0;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
flex: 1;
|
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 60px;
|
|
||||||
max-height: 120px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #667eea;
|
|
||||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-btn {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input-wrapper {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-input-wrapper input[type="file"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn {
|
|
||||||
background: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
padding: 10px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn:hover {
|
|
||||||
background: #45a049;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-image {
|
|
||||||
max-width: 150px;
|
|
||||||
max-height: 150px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-indicator {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar */
|
|
||||||
.sidebar {
|
|
||||||
width: 300px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 15px;
|
|
||||||
font-weight: bold;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-item {
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
border-left: 3px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-item:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-left-color: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.model-selector {
|
|
||||||
padding: 15px;
|
|
||||||
border-top: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
display: inline-block;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background: #667eea;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: pulse 1.5s infinite;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.5; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
background: #ffebee;
|
|
||||||
color: #c62828;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
background: #e8f5e9;
|
|
||||||
color: #2e7d32;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.container {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content {
|
|
||||||
max-width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<!-- Chat Panel -->
|
|
||||||
<div class="chat-panel">
|
|
||||||
<div class="chat-header">🤖 Code Agent Chat</div>
|
|
||||||
|
|
||||||
<div class="chat-messages" id="chatMessages"></div>
|
|
||||||
|
|
||||||
<div class="input-area">
|
|
||||||
<div class="input-container">
|
|
||||||
<textarea
|
|
||||||
id="messageInput"
|
|
||||||
placeholder="Escribe tu directive aquí... (ej: Crea un contador HTML)"
|
|
||||||
onkeydown="if(event.key==='Enter' && event.ctrlKey) sendMessage();"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="selectedImageContainer" style="display:none;">
|
|
||||||
<img id="selectedImagePreview" class="selected-image" alt="Imagen seleccionada">
|
|
||||||
<div class="image-indicator" id="imageIndicator"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-group">
|
|
||||||
<div class="file-input-wrapper">
|
|
||||||
<input type="file" id="imageInput" accept="image/*">
|
|
||||||
<button class="upload-btn" onclick="document.getElementById('imageInput').click();">
|
|
||||||
📸 Adjuntar Imagen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button class="send-btn" id="sendBtn" onclick="sendMessage();">
|
|
||||||
Enviar (Ctrl+Enter)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sidebar -->
|
|
||||||
<div class="sidebar">
|
|
||||||
<div class="sidebar-header">📁 Workspace</div>
|
|
||||||
|
|
||||||
<div class="sidebar-content" id="fileList">
|
|
||||||
<p style="color: #999; font-size: 13px;">Cargando archivos...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="model-selector">
|
|
||||||
<label style="font-size: 12px; color: #666;">Modelo LLM:</label>
|
|
||||||
<select id="modelSelect" onchange="localStorage.setItem('selectedModel', this.value)">
|
|
||||||
<option value="phi">⚡ Phi (Rápido)</option>
|
|
||||||
<option value="deepseek-coder:33b">🧠 DeepSeek (Potente)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const API_URL = "http://localhost:5000/api";
|
|
||||||
const chatMessagesDiv = document.getElementById("chatMessages");
|
|
||||||
const messageInput = document.getElementById("messageInput");
|
|
||||||
const sendBtn = document.getElementById("sendBtn");
|
|
||||||
const imageInput = document.getElementById("imageInput");
|
|
||||||
const fileList = document.getElementById("fileList");
|
|
||||||
const modelSelect = document.getElementById("modelSelect");
|
|
||||||
const selectedImageContainer = document.getElementById("selectedImageContainer");
|
|
||||||
const selectedImagePreview = document.getElementById("selectedImagePreview");
|
|
||||||
const imageIndicator = document.getElementById("imageIndicator");
|
|
||||||
|
|
||||||
let selectedImage = null;
|
|
||||||
|
|
||||||
// Load saved model
|
|
||||||
const savedModel = localStorage.getItem("selectedModel") || "phi";
|
|
||||||
modelSelect.value = savedModel;
|
|
||||||
|
|
||||||
// Manejo de imagen
|
|
||||||
imageInput.addEventListener("change", (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
selectedImage = file;
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (event) => {
|
|
||||||
selectedImagePreview.src = event.target.result;
|
|
||||||
imageIndicator.textContent = `✅ ${file.name} seleccionada`;
|
|
||||||
selectedImageContainer.style.display = "block";
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Limpiar imagen
|
|
||||||
function clearImage() {
|
|
||||||
selectedImage = null;
|
|
||||||
imageInput.value = "";
|
|
||||||
selectedImageContainer.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enviar mensaje
|
|
||||||
async function sendMessage() {
|
|
||||||
const message = messageInput.value.trim();
|
|
||||||
if (!message) return;
|
|
||||||
|
|
||||||
const model = modelSelect.value;
|
|
||||||
|
|
||||||
// Mostrar mensaje del usuario
|
|
||||||
addMessage(message, "user", selectedImage);
|
|
||||||
messageInput.value = "";
|
|
||||||
clearImage();
|
|
||||||
sendBtn.disabled = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Preparar form data
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("message", message);
|
|
||||||
formData.append("model", model);
|
|
||||||
if (selectedImage) {
|
|
||||||
formData.append("image", selectedImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enviar
|
|
||||||
const response = await fetch(`${API_URL}/chat`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const error = await response.json();
|
|
||||||
addMessage(`❌ Error: ${error.detail}`, "assistant");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Mostrar respuesta del LLM
|
|
||||||
addMessage(data.llm_response, "assistant", null, model);
|
|
||||||
|
|
||||||
// Mostrar resultado de la acción
|
|
||||||
if (data.action_result) {
|
|
||||||
const resultText = JSON.stringify(data.action_result, null, 2);
|
|
||||||
if (data.action_result.success) {
|
|
||||||
addMessage(`✅ ${data.action_result.result}`, "assistant");
|
|
||||||
} else {
|
|
||||||
addMessage(`⚠️ ${data.action_result.error || data.action_result.result}`, "assistant");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recargar archivos
|
|
||||||
loadFiles();
|
|
||||||
} catch (error) {
|
|
||||||
addMessage(`❌ Error: ${error.message}`, "assistant");
|
|
||||||
} finally {
|
|
||||||
sendBtn.disabled = false;
|
|
||||||
messageInput.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agregar mensaje al chat
|
|
||||||
function addMessage(text, sender, image = null, model = null) {
|
|
||||||
const messageDiv = document.createElement("div");
|
|
||||||
messageDiv.className = `message ${sender}`;
|
|
||||||
|
|
||||||
const contentDiv = document.createElement("div");
|
|
||||||
contentDiv.className = "message-content";
|
|
||||||
|
|
||||||
// Formatear texto (markdown básico)
|
|
||||||
let formattedText = text
|
|
||||||
.replace(/```([^`]*?)```/g, "<pre><code>$1</code></pre>")
|
|
||||||
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
|
||||||
.replace(/_(.*?)_/g, "<em>$1</em>");
|
|
||||||
|
|
||||||
contentDiv.innerHTML = formattedText;
|
|
||||||
|
|
||||||
if (image instanceof File) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = e.target.result;
|
|
||||||
img.className = "message-image";
|
|
||||||
contentDiv.appendChild(img);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageDiv.appendChild(contentDiv);
|
|
||||||
|
|
||||||
if (model) {
|
|
||||||
const meta = document.createElement("div");
|
|
||||||
meta.className = "message-meta";
|
|
||||||
meta.textContent = `Modelo: ${model}`;
|
|
||||||
messageDiv.appendChild(meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
chatMessagesDiv.appendChild(messageDiv);
|
|
||||||
chatMessagesDiv.scrollTop = chatMessagesDiv.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cargar archivos del workspace
|
|
||||||
async function loadFiles() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_URL}/files`);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
fileList.innerHTML = "";
|
|
||||||
|
|
||||||
if (data.files.length === 0) {
|
|
||||||
fileList.innerHTML = '<p style="color: #999; font-size: 13px;">Sin archivos aún</p>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.files.forEach(file => {
|
|
||||||
const item = document.createElement("div");
|
|
||||||
item.className = "file-item";
|
|
||||||
|
|
||||||
let icon = "📄";
|
|
||||||
if (file.type === ".html") icon = "🌐";
|
|
||||||
if (file.type === ".js") icon = "📜";
|
|
||||||
if (file.type === ".py") icon = "🐍";
|
|
||||||
if (file.type === ".css") icon = "🎨";
|
|
||||||
|
|
||||||
item.innerHTML = `${icon} <strong>${file.name}</strong><br>
|
|
||||||
<span style="font-size: 11px; color: #999;">${(file.size / 1024).toFixed(1)} KB</span>`;
|
|
||||||
|
|
||||||
item.onclick = async () => {
|
|
||||||
try {
|
|
||||||
const fileResponse = await fetch(`${API_URL}/file/${file.name}`);
|
|
||||||
const fileData = await fileResponse.json();
|
|
||||||
addMessage(`\`\`\`\n${fileData.content}\n\`\`\``, "assistant");
|
|
||||||
} catch (e) {
|
|
||||||
addMessage(`Error leyendo ${file.name}`, "assistant");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fileList.appendChild(item);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error cargando archivos:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cargar archivos al iniciar
|
|
||||||
loadFiles();
|
|
||||||
|
|
||||||
// Auto-refresh cada 10 segundos
|
|
||||||
setInterval(loadFiles, 10000);
|
|
||||||
|
|
||||||
// Enter para enviar
|
|
||||||
messageInput.addEventListener("keydown", (e) => {
|
|
||||||
if (e.key === "Enter" && e.ctrlKey) {
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mensaje inicial
|
|
||||||
setTimeout(() => {
|
|
||||||
addMessage("👋 Hola! Soy tu agent agentico. Puedes:\n\n- **Pedir que cree código** (HTML, Python, Node.js)\n- **Adjuntar imágenes** (capturas, diseños)\n- **Dar directivas** para modificar tus archivos\n- **Ejecutar y testear** automáticamente en el navegador\n\n¿Qué querés crear?", "assistant");
|
|
||||||
}, 500);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user