Enhancement Suite: Anti-loop protection, Kokoro TTS integration, ComfyUI status monitoring, and auto-open image functionality
This commit is contained in:
0
build/lib/ollama_cli/core/__init__.py
Normal file
0
build/lib/ollama_cli/core/__init__.py
Normal file
57
build/lib/ollama_cli/core/config.py
Normal file
57
build/lib/ollama_cli/core/config.py
Normal 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)
|
||||
87
build/lib/ollama_cli/core/ollama.py
Normal file
87
build/lib/ollama_cli/core/ollama.py
Normal 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
|
||||
53
build/lib/ollama_cli/core/sessions.py
Normal file
53
build/lib/ollama_cli/core/sessions.py
Normal 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
|
||||
Reference in New Issue
Block a user