Enhancement Suite: Anti-loop protection, Kokoro TTS integration, ComfyUI status monitoring, and auto-open image functionality

This commit is contained in:
2026-02-23 00:59:20 +00:00
parent d7efe008b8
commit 90fcba28bc
41 changed files with 1982 additions and 40 deletions

View File

View File

@@ -0,0 +1,57 @@
import os
import json
CONFIG_FILE = os.path.expanduser("~/.ollama-cli-config.json")
DEFAULT_CONFIG = {
"excluded_models": [],
"git": {
"remote_url": "",
"username": "",
"password": ""
},
"gitea": {
"url": "",
"token": ""
},
"auto_save_sessions": True,
"mcp_servers": {},
"ollama_url": "http://localhost:11434",
"comfy_url": "http://localhost:9000",
"piper": {
"path": "~/.ollama-cli/piper/piper",
"model": "~/.ollama-cli/piper/voice.onnx"
},
"ntfy": {
"enabled": False,
"topic": "",
"server": "https://ntfy.sh"
},
"default_model": "llama3.2"
}
def load_config():
"""Load config file"""
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r') as f:
config = json.load(f)
# Merge with defaults to ensure all keys exist
for k, v in DEFAULT_CONFIG.items():
if k not in config:
config[k] = v
return config
except Exception:
pass
return DEFAULT_CONFIG.copy()
def save_config(config):
"""Save config file"""
with open(CONFIG_FILE, 'w') as f:
json.dump(config, f, indent=2)
def update_config(key, value):
"""Update a specific config key"""
config = load_config()
config[key] = value
save_config(config)

View File

@@ -0,0 +1,87 @@
import json
import requests
import subprocess
import sys
from typing import List, Dict, Optional, Generator
class OllamaClient:
def __init__(self, base_url: str = "http://localhost:11434"):
# Strip trailing slashes and common API paths if user provided them in config
self.base_url = base_url.rstrip("/")
if self.base_url.endswith("/api/chat"):
self.base_url = self.base_url[:-9]
elif self.base_url.endswith("/api"):
self.base_url = self.base_url[:-4]
self.chat_url = f"{self.base_url}/api/chat"
self.list_url = f"{self.base_url}/api/tags"
def get_available_models(self) -> List[str]:
"""Get list of models available in Ollama"""
try:
# Try API first
response = requests.get(self.list_url, timeout=5)
if response.status_code == 200:
data = response.json()
return [m['name'] for m in data.get('models', [])]
# Fallback to CLI
result = subprocess.run("ollama list", shell=True, capture_output=True, text=True, timeout=5)
models = []
for line in result.stdout.split('\n')[1:]: # Skip header
if line.strip():
models.append(line.split()[0])
return models
except Exception:
return []
def chat(self, messages: List[Dict], model: str, stream: bool = True) -> Generator[str, None, str]:
"""Call Ollama API with streaming"""
try:
response = requests.post(self.chat_url, json={
"model": model,
"messages": messages,
"stream": stream
}, stream=stream, timeout=60)
response.raise_for_status()
full_response = ""
if stream:
for line in response.iter_lines():
if line:
chunk = json.loads(line)
if "message" in chunk and "content" in chunk["message"]:
content = chunk["message"]["content"]
full_response += content
yield content
return full_response
else:
data = response.json()
return data.get("message", {}).get("content", "")
except requests.exceptions.ConnectionError:
raise ConnectionError(f"Cannot connect to Ollama at {self.base_url}. Make sure it's running.")
except Exception as e:
raise Exception(f"Error calling Ollama: {str(e)}")
def select_best_model(self, user_input: str, available_models: List[str], default_model: str) -> str:
"""Select best model based on task type"""
if not available_models:
return default_model
# Model preferences
code_models = ["qwen2.5-coder", "mistral-nemo", "codellama", "deepseek-coder"]
general_models = ["llama3.2", "llama3.1", "mistral"]
# Simple heuristic
code_keywords = ['code', 'script', 'program', 'function', 'debug', 'python', 'javascript']
is_code_task = any(k in user_input.lower() for k in code_keywords)
target_list = code_models if is_code_task else general_models
for target in target_list:
for available in available_models:
if target in available.lower():
return available
return available_models[0] if available_models else default_model

View File

@@ -0,0 +1,53 @@
import os
import json
from datetime import datetime
from typing import List, Dict, Optional
SESSION_DIR = os.path.expanduser("~/.ollama-cli-sessions")
def save_session(messages: List[Dict], session_name: Optional[str] = None) -> str:
"""Save conversation session"""
os.makedirs(SESSION_DIR, exist_ok=True)
if not session_name:
session_name = datetime.now().strftime("%Y%m%d_%H%M%S")
# Ensure filename is safe
safe_name = "".join([c for c in session_name if c.isalnum() or c in (' ', '.', '_', '-')]).strip()
session_file = os.path.join(SESSION_DIR, f"{safe_name}.json")
with open(session_file, 'w') as f:
json.dump({
"messages": messages,
"timestamp": datetime.now().isoformat(),
"name": session_name
}, f, indent=2)
return session_file
def load_session(session_name: str) -> Optional[List[Dict]]:
"""Load conversation session"""
session_file = os.path.join(SESSION_DIR, f"{session_name}.json")
if not os.path.exists(session_file):
# Try without extension if name doesn't have it
if not session_name.endswith('.json'):
session_file = os.path.join(SESSION_DIR, f"{session_name}.json")
if os.path.exists(session_file):
with open(session_file, 'r') as f:
data = json.load(f)
return data.get("messages", [])
return None
def list_sessions() -> List[str]:
"""List available sessions"""
if not os.path.exists(SESSION_DIR):
return []
sessions = [f.replace('.json', '') for f in os.listdir(SESSION_DIR) if f.endswith('.json')]
return sorted(sessions, reverse=True)
def delete_session(session_name: str) -> bool:
"""Delete a session"""
session_file = os.path.join(SESSION_DIR, f"{session_name}.json")
if os.path.exists(session_file):
os.remove(session_file)
return True
return False