mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-18 10:02:16 +00:00
Update notification service
This commit is contained in:
@@ -36,7 +36,7 @@ if BASE_DIR not in sys.path:
|
||||
|
||||
from notification_channels import create_channel, CHANNEL_TYPES
|
||||
from notification_templates import (
|
||||
render_template, format_with_ai, enrich_with_emojis, TEMPLATES,
|
||||
render_template, format_with_ai, format_with_ai_full, enrich_with_emojis, TEMPLATES,
|
||||
EVENT_GROUPS, get_event_types_by_group, get_default_enabled_events
|
||||
)
|
||||
from notification_events import (
|
||||
@@ -743,12 +743,14 @@ class NotificationManager:
|
||||
# ── Per-channel AI enhancement ──
|
||||
# Apply AI with channel-specific detail level and emoji setting
|
||||
# If AI is enabled AND rich_format is on, AI will include emojis directly
|
||||
ch_body = format_with_ai(
|
||||
ai_result = format_with_ai_full(
|
||||
ch_title, ch_body, severity, ai_config,
|
||||
detail_level=detail_level,
|
||||
journal_context=journal_context,
|
||||
use_emojis=use_rich_format
|
||||
)
|
||||
ch_title = ai_result.get('title', ch_title)
|
||||
ch_body = ai_result.get('body', ch_body)
|
||||
|
||||
# Fallback emoji enrichment only if AI is disabled but rich_format is on
|
||||
# (If AI processed the message with emojis, this is skipped)
|
||||
@@ -1055,17 +1057,19 @@ class NotificationManager:
|
||||
rich_key = f'{ch_name}.rich_format'
|
||||
use_rich_format = self._config.get(rich_key, 'false') == 'true'
|
||||
|
||||
ch_message = format_with_ai(
|
||||
ai_result = format_with_ai_full(
|
||||
title, message, severity, ai_config,
|
||||
detail_level=detail_level,
|
||||
use_emojis=use_rich_format
|
||||
)
|
||||
ch_title = ai_result.get('title', title)
|
||||
ch_message = ai_result.get('body', message)
|
||||
|
||||
result = channel.send(title, ch_message, severity, data)
|
||||
result = channel.send(ch_title, ch_message, severity, data)
|
||||
results[ch_name] = result
|
||||
|
||||
self._record_history(
|
||||
event_type, ch_name, title, ch_message, severity,
|
||||
event_type, ch_name, ch_title, ch_message, severity,
|
||||
result.get('success', False),
|
||||
result.get('error', ''),
|
||||
source
|
||||
@@ -1138,7 +1142,7 @@ class NotificationManager:
|
||||
}
|
||||
|
||||
# ProxMenux logo for welcome message
|
||||
logo_url = 'https://macrimi.github.io/ProxMenux/logo.png'
|
||||
logo_url = 'https://proxmenux.com/telegram.png'
|
||||
|
||||
for ch_name, channel in targets.items():
|
||||
try:
|
||||
@@ -1150,14 +1154,16 @@ class NotificationManager:
|
||||
use_rich_format = self._config.get(rich_key, 'false') == 'true'
|
||||
|
||||
# Apply AI enhancement (translates to configured language)
|
||||
enhanced_message = format_with_ai(
|
||||
ai_result = format_with_ai_full(
|
||||
base_title, base_message, 'INFO', ai_config,
|
||||
detail_level=detail_level,
|
||||
use_emojis=use_rich_format
|
||||
)
|
||||
enhanced_title = ai_result.get('title', base_title)
|
||||
enhanced_message = ai_result.get('body', base_message)
|
||||
|
||||
# Send message
|
||||
send_result = channel.send(base_title, enhanced_message, 'INFO')
|
||||
send_result = channel.send(enhanced_title, enhanced_message, 'INFO')
|
||||
success = send_result.get('success', False)
|
||||
error = send_result.get('error', '')
|
||||
|
||||
@@ -1168,7 +1174,7 @@ class NotificationManager:
|
||||
results[ch_name] = {'success': success, 'error': error}
|
||||
|
||||
self._record_history(
|
||||
'test', ch_name, base_title,
|
||||
'test', ch_name, enhanced_title,
|
||||
enhanced_message[:500], 'INFO',
|
||||
success, error, 'api'
|
||||
)
|
||||
|
||||
@@ -1244,13 +1244,14 @@ AI_SYSTEM_PROMPT = """You are a technical assistant for ProxMenux Monitor, a Pro
|
||||
Your task is to translate and format system alerts to {language}.
|
||||
|
||||
STRICT RULES:
|
||||
1. Translate the message to the requested language
|
||||
2. Maintain an INFORMATIVE and OBJECTIVE tone
|
||||
3. DO NOT use formal introductions ("Dear...", "Esteemed...")
|
||||
4. DO NOT give recommendations or action suggestions
|
||||
5. DO NOT interpret data subjectively
|
||||
6. Present only FACTS and TECHNICAL DATA
|
||||
7. Respect the requested detail level: {detail_level}
|
||||
1. Translate BOTH the title and message body to {language}
|
||||
2. DO NOT use markdown formatting like **bold** or *italic*
|
||||
3. Use plain text only - no special formatting syntax
|
||||
4. Maintain an INFORMATIVE and OBJECTIVE tone
|
||||
5. DO NOT use formal introductions ("Dear...", "Esteemed...")
|
||||
6. DO NOT give recommendations or action suggestions
|
||||
7. Present only FACTS and TECHNICAL DATA
|
||||
8. Respect the requested detail level: {detail_level}
|
||||
{emoji_instructions}
|
||||
|
||||
DETAIL LEVELS:
|
||||
@@ -1262,30 +1263,71 @@ MESSAGE TYPES:
|
||||
- Some messages come from Proxmox VE webhooks with raw system data (backup logs, update lists, SMART errors)
|
||||
- Parse and present this data clearly, extracting key information (VM IDs, sizes, durations, errors)
|
||||
- For backup messages: highlight status (OK/ERROR), VM names, sizes, and duration
|
||||
- For update messages: list package names and counts
|
||||
- For update messages: list package names and counts clearly formatted
|
||||
- For disk/SMART errors: highlight affected device and error type
|
||||
|
||||
OUTPUT FORMAT (VERY IMPORTANT):
|
||||
You MUST return the response in this exact format with these exact markers:
|
||||
[TITLE]
|
||||
Translated title here
|
||||
[BODY]
|
||||
Translated message body here
|
||||
|
||||
- The [TITLE] section should contain ONLY the translated title (short, one line)
|
||||
- The [BODY] section contains the translated and formatted message
|
||||
- Do NOT include the markers [TITLE] or [BODY] as part of the content
|
||||
- Start body content directly (emoji if enabled, then text)
|
||||
|
||||
If journal log context is provided, use it for more precise event information."""
|
||||
|
||||
# Emoji instructions for rich format channels
|
||||
AI_EMOJI_INSTRUCTIONS = """
|
||||
8. ENRICH with contextual emojis and icons:
|
||||
- Use appropriate emojis at the START of the title/message to indicate severity and type
|
||||
- Severity indicators: Use a colored circle at the start (info=blue, warning=yellow, critical=red)
|
||||
- Add relevant technical emojis: disk, server, network, security, backup, etc.
|
||||
- Keep emojis contextual and professional, not decorative
|
||||
- Examples of appropriate emojis:
|
||||
* Disk/Storage: disk, folder, file
|
||||
* Network: globe, signal, connection
|
||||
* Security: shield, lock, key, warning
|
||||
* System: gear, server, computer
|
||||
* Status: checkmark, cross, warning, info
|
||||
* Backup: save, sync, cloud
|
||||
* Performance: chart, speedometer"""
|
||||
10. ENRICH with contextual emojis:
|
||||
- Start with a severity indicator circle: (blue=info), (yellow=warning), (red=critical)
|
||||
- Add specific emojis for each data item, not just at the start
|
||||
- Use emojis that match the content type precisely:
|
||||
|
||||
UPDATES/PACKAGES:
|
||||
- Total updates count
|
||||
- Security updates
|
||||
- Proxmox updates
|
||||
- Kernel updates
|
||||
- Package list items (bullet points)
|
||||
|
||||
BACKUP/STORAGE:
|
||||
- Backup status
|
||||
- Storage/disk
|
||||
- Sync/transfer
|
||||
- Folder/directory
|
||||
- Size/capacity
|
||||
|
||||
SYSTEM/HARDWARE:
|
||||
- Server/host
|
||||
- Container/VM
|
||||
- CPU/processor
|
||||
- Memory/RAM
|
||||
- Temperature
|
||||
|
||||
NETWORK:
|
||||
- Network/connection
|
||||
- Speed/bandwidth
|
||||
- Globe/internet
|
||||
|
||||
SECURITY/ALERTS:
|
||||
- Warning/alert
|
||||
- Security/shield
|
||||
- Error/problem
|
||||
- Lock/authentication
|
||||
|
||||
STATUS:
|
||||
- Success/OK
|
||||
- Failed/error
|
||||
- Running/active
|
||||
- Stopped/inactive"""
|
||||
|
||||
# No emoji instructions for email/plain channels
|
||||
AI_NO_EMOJI_INSTRUCTIONS = """
|
||||
8. DO NOT use emojis or special icons - plain text only for email compatibility"""
|
||||
10. DO NOT use emojis or special icons - plain text only for email compatibility"""
|
||||
|
||||
|
||||
class AIEnhancer:
|
||||
@@ -1343,7 +1385,7 @@ class AIEnhancer:
|
||||
def enhance(self, title: str, body: str, severity: str,
|
||||
detail_level: str = 'standard',
|
||||
journal_context: str = '',
|
||||
use_emojis: bool = False) -> Optional[str]:
|
||||
use_emojis: bool = False) -> Optional[Dict[str, str]]:
|
||||
"""Enhance/translate notification with AI.
|
||||
|
||||
Args:
|
||||
@@ -1355,7 +1397,7 @@ class AIEnhancer:
|
||||
use_emojis: Whether to include emojis in the response (for push channels)
|
||||
|
||||
Returns:
|
||||
Enhanced/translated text or None if failed
|
||||
Dict with 'title' and 'body' keys, or None if failed
|
||||
"""
|
||||
if not self._provider:
|
||||
return None
|
||||
@@ -1384,11 +1426,49 @@ class AIEnhancer:
|
||||
|
||||
try:
|
||||
result = self._provider.generate(system_prompt, user_msg, max_tokens)
|
||||
return result
|
||||
return self._parse_ai_response(result, title, body)
|
||||
except Exception as e:
|
||||
print(f"[AIEnhancer] Enhancement failed: {e}")
|
||||
return None
|
||||
|
||||
def _parse_ai_response(self, response: str, original_title: str, original_body: str) -> Dict[str, str]:
|
||||
"""Parse AI response to extract title and body.
|
||||
|
||||
Args:
|
||||
response: Raw AI response text
|
||||
original_title: Original title as fallback
|
||||
original_body: Original body as fallback
|
||||
|
||||
Returns:
|
||||
Dict with 'title' and 'body' keys
|
||||
"""
|
||||
if not response:
|
||||
return {'title': original_title, 'body': original_body}
|
||||
|
||||
# Try to parse [TITLE] and [BODY] markers
|
||||
title_marker = '[TITLE]'
|
||||
body_marker = '[BODY]'
|
||||
|
||||
title_start = response.find(title_marker)
|
||||
body_start = response.find(body_marker)
|
||||
|
||||
if title_start != -1 and body_start != -1:
|
||||
# Extract title (between [TITLE] and [BODY])
|
||||
title_content = response[title_start + len(title_marker):body_start].strip()
|
||||
# Extract body (after [BODY])
|
||||
body_content = response[body_start + len(body_marker):].strip()
|
||||
|
||||
return {
|
||||
'title': title_content if title_content else original_title,
|
||||
'body': body_content if body_content else original_body
|
||||
}
|
||||
|
||||
# Fallback: if markers not found, use whole response as body
|
||||
return {
|
||||
'title': original_title,
|
||||
'body': response.strip()
|
||||
}
|
||||
|
||||
def test_connection(self) -> Dict[str, Any]:
|
||||
"""Test the AI provider connection.
|
||||
|
||||
@@ -1426,22 +1506,47 @@ def format_with_ai(title: str, body: str, severity: str,
|
||||
Returns:
|
||||
Enhanced body string or original if AI fails
|
||||
"""
|
||||
result = format_with_ai_full(title, body, severity, ai_config, detail_level, journal_context, use_emojis)
|
||||
return result.get('body', body)
|
||||
|
||||
|
||||
def format_with_ai_full(title: str, body: str, severity: str,
|
||||
ai_config: Dict[str, Any],
|
||||
detail_level: str = 'standard',
|
||||
journal_context: str = '',
|
||||
use_emojis: bool = False) -> Dict[str, str]:
|
||||
"""Format a message with AI enhancement/translation, returning both title and body.
|
||||
|
||||
Args:
|
||||
title: Notification title
|
||||
body: Notification body
|
||||
severity: Severity level
|
||||
ai_config: Configuration dictionary with AI settings
|
||||
detail_level: Level of detail (brief, standard, detailed)
|
||||
journal_context: Optional journal log context
|
||||
use_emojis: Whether to include emojis (for push channels like Telegram/Discord)
|
||||
|
||||
Returns:
|
||||
Dict with 'title' and 'body' keys (translated/enhanced)
|
||||
"""
|
||||
default_result = {'title': title, 'body': body}
|
||||
|
||||
# Check if AI is enabled
|
||||
ai_enabled = ai_config.get('ai_enabled')
|
||||
if isinstance(ai_enabled, str):
|
||||
ai_enabled = ai_enabled.lower() == 'true'
|
||||
|
||||
if not ai_enabled:
|
||||
return body
|
||||
return default_result
|
||||
|
||||
# Check for API key (not required for Ollama)
|
||||
provider = ai_config.get('ai_provider', 'groq')
|
||||
if provider != 'ollama' and not ai_config.get('ai_api_key'):
|
||||
return body
|
||||
return default_result
|
||||
|
||||
# For Ollama, check URL is configured
|
||||
if provider == 'ollama' and not ai_config.get('ai_ollama_url'):
|
||||
return body
|
||||
return default_result
|
||||
|
||||
# Create enhancer and process
|
||||
enhancer = AIEnhancer(ai_config)
|
||||
@@ -1452,15 +1557,19 @@ def format_with_ai(title: str, body: str, severity: str,
|
||||
use_emojis=use_emojis
|
||||
)
|
||||
|
||||
# Return enhanced text if successful, otherwise original
|
||||
if enhanced:
|
||||
# Return enhanced result if successful, otherwise original
|
||||
if enhanced and isinstance(enhanced, dict):
|
||||
result_title = enhanced.get('title', title)
|
||||
result_body = enhanced.get('body', body)
|
||||
|
||||
# For detailed level (email), append original message for reference
|
||||
# This ensures full technical data is available even after AI processing
|
||||
if detail_level == 'detailed' and body and len(body) > 50:
|
||||
# Only append if original has substantial content
|
||||
enhanced += "\n\n" + "-" * 40 + "\n"
|
||||
enhanced += "Original message:\n"
|
||||
enhanced += body
|
||||
return enhanced
|
||||
result_body += "\n\n" + "-" * 40 + "\n"
|
||||
result_body += "Original message:\n"
|
||||
result_body += body
|
||||
|
||||
return {'title': result_title, 'body': result_body}
|
||||
|
||||
return body
|
||||
return default_result
|
||||
|
||||
Reference in New Issue
Block a user