gpu-poor-llm-arena / model_suggestions.py
k-mktr's picture
Update model_suggestions.py
dcde71a verified
from nc_py_api import Nextcloud
import json
from typing import Dict
from datetime import datetime
import os
import re
import config
# Initialize Nextcloud client
nc = Nextcloud(
nextcloud_url=config.NEXTCLOUD_URL,
nc_auth_user=config.NEXTCLOUD_USERNAME,
nc_auth_pass=config.NEXTCLOUD_PASSWORD
)
def validate_model_url(url: str) -> bool:
"""
Validate if the provided URL matches the expected format.
Accepts both direct Ollama models and HuggingFace GGUF models.
"""
# Pattern for HuggingFace GGUF models
hf_pattern = r'^hf\.co/[\w-]+/[\w\.-]+(?:-GGUF)?:Q[0-9]+(?:_[A-Z0-9_]+)?$'
# Pattern for direct Ollama models
ollama_pattern = r'^[\w\.-]+(?::\d+(?:\.\d+)?[b])?(?:-[\w-]+)?:Q[0-9]+(?:_[A-Z0-9_]+)?$'
return bool(re.match(hf_pattern, url) or re.match(ollama_pattern, url))
def load_suggestions() -> Dict:
"""Load suggestions from Nextcloud with local file fallback."""
try:
# Try to load from Nextcloud
remote_data = nc.files.download(config.NEXTCLOUD_SUGGESTIONS_PATH)
if remote_data:
suggestions = json.loads(remote_data.decode('utf-8'))
# Update local cache
with open('model_suggestions.json', 'w') as f:
json.dump(suggestions, f, indent=2)
return suggestions
except Exception as e:
print(f"Could not load from Nextcloud: {e}")
# Try local cache
if os.path.exists('model_suggestions.json'):
try:
with open('model_suggestions.json', 'r') as f:
return json.load(f)
except Exception as e:
print(f"Could not load from local cache: {e}")
# Initialize new suggestions if both attempts fail
return {
"suggestions": {},
"last_updated": datetime.now().isoformat(),
"total_suggestions": 0
}
def save_suggestions(suggestions: Dict) -> bool:
"""Save suggestions to both Nextcloud and local cache."""
try:
# Update metadata
suggestions["last_updated"] = datetime.now().isoformat()
suggestions["total_suggestions"] = sum(s["count"] for s in suggestions["suggestions"].values())
# Save to Nextcloud
json_data = json.dumps(suggestions, indent=2)
nc.files.upload(config.NEXTCLOUD_SUGGESTIONS_PATH, json_data.encode('utf-8'))
# Update local cache
with open('model_suggestions.json', 'w') as f:
json.dump(suggestions, f, indent=2)
return True
except Exception as e:
print(f"Error saving suggestions: {e}")
return False
def add_suggestion(model_url: str) -> str:
"""Add or update a model suggestion with validation."""
# Validate model URL format
if not validate_model_url(model_url):
return ("❌ Invalid model URL format. Please use either:\n"
"- Ollama format: model-name:Q4_K_M\n"
"- HuggingFace format: hf.co/username/model-name-GGUF:Q4_K_M")
# Check if model is already approved
if model_url in dict(config.get_approved_models()):
return "ℹ️ This model is already in the arena!"
suggestions = load_suggestions()
current_time = datetime.now().isoformat()
if model_url in suggestions["suggestions"]:
suggestions["suggestions"][model_url].update({
"count": suggestions["suggestions"][model_url]["count"] + 1,
"last_suggested": current_time
})
message = (f"✨ Model suggestion updated! "
f"This model has been suggested {suggestions['suggestions'][model_url]['count']} times.")
else:
suggestions["suggestions"][model_url] = {
"count": 1,
"first_suggested": current_time,
"last_suggested": current_time
}
message = "βœ… New model suggestion recorded successfully!"
if save_suggestions(suggestions):
return message
return "❌ Error saving suggestion. Please try again later."
def get_suggestions_html() -> str:
"""Generate HTML table of model suggestions with improved styling."""
suggestions = load_suggestions()
# Sort suggestions by count (descending) and last suggested date
sorted_suggestions = sorted(
suggestions["suggestions"].items(),
key=lambda x: (x[1]["count"], x[1]["last_suggested"]),
reverse=True
)
stats_header = f"""
<div class="stats-header">
Total Suggestions: {suggestions.get("total_suggestions", 0)} | Last Updated: {suggestions.get("last_updated", "Never").split("T")[0]}
</div>
"""
html = f"""
<style>
.suggestions-table {{
width: 100%;
border-collapse: collapse;
font-family: Arial, sans-serif;
margin-top: 20px;
}}
.suggestions-table th, .suggestions-table td {{
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}}
.suggestions-table th {{
background-color: rgba(255, 255, 255, 0.1);
font-weight: bold;
}}
.rank-column {{
width: 60px;
text-align: center;
}}
.count-badge {{
background-color: rgba(34, 87, 122, 0.7);
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.9em;
}}
.stats-header {{
font-size: 0.9em;
color: #888;
margin-bottom: 10px;
}}
</style>
{stats_header}
<table class='suggestions-table'>
<tr>
<th class='rank-column'>Rank</th>
<th>Model URL</th>
<th>Suggestions</th>
<th>First Suggested</th>
<th>Last Suggested</th>
</tr>
"""
for index, (model_url, data) in enumerate(sorted_suggestions, start=1):
rank_display = {1: "πŸ₯‡", 2: "πŸ₯ˆ", 3: "πŸ₯‰"}.get(index, f"{index}")
html += f"""
<tr>
<td class='rank-column'>{rank_display}</td>
<td>{model_url}</td>
<td><span class="count-badge">{data['count']}</span></td>
<td>{data['first_suggested'].split('T')[0]}</td>
<td>{data['last_suggested'].split('T')[0]}</td>
</tr>
"""
html += "</table>"
return html